1.4. palindrome permutation

java, c++ and python solutions. use of bitvector
This commit is contained in:
anebz 2019-03-29 14:24:19 +01:00
parent ff70f806fb
commit 5fd11241b5
4 changed files with 273 additions and 0 deletions

View File

@ -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));
}

View File

@ -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));
}
}

View File

@ -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.

View File

@ -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()