1.4. palindrome permutation
java, c++ and python solutions. use of bitvector
This commit is contained in:
parent
ff70f806fb
commit
5fd11241b5
|
|
@ -0,0 +1,72 @@
|
|||
#include <iostream>
|
||||
|
||||
/*
|
||||
* Helper routine to return an frequency Table index
|
||||
*/
|
||||
|
||||
int getCharIndex(char c) {
|
||||
int idx = -1;
|
||||
if (c >= 'a' && c <= 'z') {
|
||||
idx = c - 'a';
|
||||
}
|
||||
else if (c >= 'A' && c <= 'Z') {
|
||||
idx = c - 'A';
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
|
||||
/*
|
||||
* Function : countFrequency
|
||||
* Args : input string, an array of int
|
||||
* Return : Void, array of int will populate each letter's frequency in string.
|
||||
*/
|
||||
|
||||
void countFrequency(const std::string &str, int *frequency) {
|
||||
int idx;
|
||||
for (const char &c : str) {
|
||||
idx = getCharIndex(c);
|
||||
if (idx != -1) {
|
||||
++frequency[idx];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int toggle(int bitVector, int index) {
|
||||
if (index < 0)
|
||||
return bitVector;
|
||||
|
||||
int mask = 1 << index;
|
||||
//if bit is not set
|
||||
if ((bitVector & mask) == 0){
|
||||
bitVector |= mask;
|
||||
} else { //if bit is set
|
||||
bitVector &= ~mask;
|
||||
}
|
||||
return bitVector;
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper functiont to find if a single bit is set
|
||||
* i.e. if bitVector is a multiple of power of 2
|
||||
*/
|
||||
|
||||
bool isExactlyOneBitSet(int bitVector) {
|
||||
return ((bitVector & (bitVector - 1)) == 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Third approach solution
|
||||
* toggle bit represent the respective char
|
||||
* for each appearance in the string.
|
||||
*/
|
||||
|
||||
bool isPermutationOfPallindrome(const std::string & str) {
|
||||
int bitVector = 0;
|
||||
int id = 0;
|
||||
for (const char & c : str)
|
||||
{
|
||||
id = getCharIndex(c);
|
||||
bitVector = toggle(bitVector, id);
|
||||
}
|
||||
return (bitVector == 0 || isExactlyOneBitSet(bitVector));
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
public class pal_perm {
|
||||
/* Toggle the ith bit in the integer. */
|
||||
public static int toggle(int bitVector, int index) {
|
||||
if (index < 0) return bitVector;
|
||||
|
||||
int mask = 1 << index;
|
||||
if ((bitVector & mask) == 0) {
|
||||
bitVector |= mask;
|
||||
} else {
|
||||
bitVector &= ~mask;
|
||||
}
|
||||
return bitVector;
|
||||
}
|
||||
|
||||
/* Create bit vector for string. For each letter with value i,
|
||||
* toggle the ith bit. */
|
||||
public static int createBitVector(String phrase) {
|
||||
int bitVector = 0;
|
||||
for (char c : phrase.toCharArray()) {
|
||||
int x = Common.getCharNumber(c);
|
||||
bitVector = toggle(bitVector, x);
|
||||
}
|
||||
return bitVector;
|
||||
}
|
||||
|
||||
/* Check that at most one bit is set by subtracting one from the
|
||||
* integer and ANDing it with the original integer. */
|
||||
public static boolean checkAtMostOneBitSet(int bitVector) {
|
||||
return (bitVector & (bitVector - 1)) == 0;
|
||||
}
|
||||
|
||||
public static boolean isPermutationOfPalindrome(String phrase) {
|
||||
int bitVector = createBitVector(phrase);
|
||||
return checkAtMostOneBitSet(bitVector);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
String pali = "Rats live on no evil star";
|
||||
System.out.println(isPermutationOfPalindrome(pali));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
# 1.4. Palindrome permutation
|
||||
|
||||
## Given a string, write a function to check if it's a permutation(rearrangement of letters) of a palindrome (word/phrase that's the same forwards or backwards). The palindrome doesn't have to be just dictionary words, ignore casing and non-letter characters
|
||||
|
||||
```python
|
||||
s = s.lower()
|
||||
for char in s:
|
||||
# if not a letter
|
||||
if ord(char) not in range (65, 91) and ord(char) not in range(97, 123):
|
||||
s.replace(char, '')
|
||||
```
|
||||
|
||||
> example
|
||||
|
||||
* input: 'Tact Coa'
|
||||
* output: True (permutations: 'taco cat', 'atco cat', etc)
|
||||
|
||||
## First idea
|
||||
|
||||
Calculate all permutations, check if palindromes. if one is, break and return True. O(n! + n * n/2). Nope.
|
||||
|
||||
## Second idea
|
||||
|
||||
What is a palindrome? Even count of characters if length of string is even, if string length is odd... at most one character with count = 1. Also, have to be arranged in order but since we're looking for palindrome permutation, this doesn't matter.
|
||||
|
||||
> test
|
||||
|
||||
* input: 'asdfdsa', True
|
||||
* input: 'asdadsa', what now?
|
||||
|
||||
Palindrome permutation: there can't be 1+ characters with count odd.
|
||||
|
||||
> test
|
||||
|
||||
* input: 'asdfdsa', True ('f':1): correct
|
||||
* input: 'asdadsa', True ('a': 3): correct
|
||||
* input: 'assdadsa', False ('a':3, 's':3): correct
|
||||
* if string length <= 1, True
|
||||
* if string length == 2, return s[0] == s[1]
|
||||
|
||||
## Second idea implementation
|
||||
|
||||
Sort string and count frequencies, O(nlogn + n)
|
||||
|
||||
## Second idea better implementation
|
||||
|
||||
While counting frequencies, have a flag for odd/no odd. Keep updating the flag, at the end return flag.
|
||||
|
||||
```python
|
||||
def pal_perm(s):
|
||||
if len(s) <= 1:
|
||||
return True
|
||||
freq = {}
|
||||
flag = 0
|
||||
for char in s:
|
||||
if char not in freq:
|
||||
freq[char] = 1
|
||||
flag += 1
|
||||
else:
|
||||
freq[char] += 1
|
||||
if freq[char] % 2 == 0:
|
||||
flag -= 1
|
||||
else:
|
||||
flag += 1
|
||||
return flag <= 1
|
||||
```
|
||||
|
||||
At the beginning, flag is true. If we add any new character, flag always false. If we update a character frequency, if odd -> even,
|
||||
|
||||
Flag needs 3 states:
|
||||
|
||||
1. all characters are even
|
||||
2. there's one character with frequency odd
|
||||
3. there's 1+ characters with frequency odd
|
||||
|
||||
Flow:
|
||||
|
||||
* if new character added, flag += 1
|
||||
* if known character has one more occurrence and freq is odd, flag -= 1
|
||||
* if known character has one more occurrence and freq is even, flag += 1
|
||||
* return if flag <= 1
|
||||
|
||||
> Implementation test:
|
||||
|
||||
* input 'asddsa', flag=0, True
|
||||
* input 'asdfdsa', flag=1, True
|
||||
* input 'asdadsa', flag=1, True
|
||||
* input 'assdadsa', flag=2, False
|
||||
|
||||
Tests passed in python. O(n) time, O(1) space.
|
||||
|
||||
## Solution
|
||||
|
||||
We need an even number of almost all characters, so that half can be on one side and half on the other. At most one character (the middle character) can have an odd count. A string can have no more than one character that is odd (nice)
|
||||
|
||||
### Solution \#1
|
||||
|
||||
Hash table to count frequency of characters, then check.
|
||||
|
||||
### Solution \#2
|
||||
|
||||
Keep track of odd counts iteratively.
|
||||
|
||||
### Solution \#3
|
||||
|
||||
We don't need to know the counts, just if it's even or odd. We can use an integer as a bit vector. When we see a letter, we map it to an integer between 0 and 25 (assuming English alphabet). Then we toggle the bit at that value.
|
||||
|
||||
ane: this is like keeping a bool array/string of len=26.
|
||||
|
||||
At the end of the iteration, we check that at most one bit in the integer is set to 1. How do we check that the integer has one '1'?
|
||||
|
||||
ex: 00010000. If we substract 1, integer is 00001111. There's no overlap between these numbers: this is correct.
|
||||
|
||||
00010100 - 1 = 00010010, and overlap = 1. Overlap is AND, 0&0=0, 1&0=0, 1&1=1.
|
||||
|
||||
`(x-1) AND x == 0`. Even if this is done when x=0, 00000000 AND 11111111 = 0 -> correct. See `1.4. palindrome_permutation.java` for bitvector implementation.
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
import unittest
|
||||
|
||||
def pal_perm(s):
|
||||
s = s.lower()
|
||||
|
||||
if len(s) <= 1:
|
||||
return True
|
||||
if len(s) == 2:
|
||||
return s[0] == s[1]
|
||||
|
||||
freq = {}
|
||||
flag = 0
|
||||
for char in s:
|
||||
# if not a letter
|
||||
if ord(char) not in range(65, 91) and ord(char) not in range(97, 123):
|
||||
s.replace(char, '')
|
||||
|
||||
if char not in freq:
|
||||
freq[char] = 1
|
||||
flag += 1
|
||||
else:
|
||||
freq[char] += 1
|
||||
if freq[char] % 2 == 0:
|
||||
flag -= 1
|
||||
else:
|
||||
flag += 1
|
||||
return flag <= 1
|
||||
|
||||
|
||||
class Test(unittest.TestCase):
|
||||
dataT = ['', 'a', 'aa', 'asddsa', 'asdfdsa', 'asdadsa', 'aaaaa', 'aaaa']
|
||||
dataF = ['as', 'ane', 'adsa', 'assdadsa']
|
||||
|
||||
def test_unique(self):
|
||||
for test in self.dataT:
|
||||
res = pal_perm(test)
|
||||
self.assertTrue(res)
|
||||
|
||||
for test in self.dataF:
|
||||
res = pal_perm(test)
|
||||
self.assertFalse(res)
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Loading…
Reference in New Issue