Palindrome Permutation Problem

Palindrome Permutation: Given a string write a function to check if a string is a permutation of a palindrome.

Observe that in order for a word to be a palindrome at most one character can have an odd count. All the other characters must have even counts. Knowing this, there are various solutions to this problem.

1. The first and most obvious solution is to count the frequencies of each character and make sure that the above property is satisfied.

2. Assume input string contains only ASCII characters. Based on this assumption, maintatin a fixed sized buffer of length 128 (regular, unextended, ASCII has 128 character encodings). Let each index i in this buffer indicate the frequency of the character in our input whose ASCII code is i. That said, instead of maintaing the actual character frequency at i we will simply flip the value from 0 to 1 if the freq. is odd and 1 to 0 if it even. At the end we can apply a reduce() function to calculate the sum of the buffer and return false if sum > 1, and otherwise return true.

1
2
3
4
5
6
7
8
9
10
11
12
13
  from functools import reduce

  def is_palindrome(s1):  
    buf = [0] * 128
    for char in s1:
      if (buf[ord(char)] == 0):
        buf[ord(char)] = 1      
      else:
        buf[ord(char)] = 0 

    return reduce(lambda x, y: x + y, buf) <= 1

  print(is_palindrome("tacocat"))

3. A small “improvement” to the solution above is to simply maintain the number of odd character frequencies as we go and return True or False depending on the number of odd character frequencies without having to use reduce() at the end of the function.

1
2
3
4
5
6
7
8
9
10
11
12
  def is_palindrome(s1):
    count_odds = 0
    buf = [0] * 128
    for char in s1:
      if (buf[ord(char)] == 0):
        buf[ord(char)] = 1
        count_odds += 1
      else:
        buf[ord(char)] = 0
        count_odds -= 1
      print(count_odds)
    return count_odds <= 1

This, however, does not change the time complexity which is still O(n) where n = the length of s1.

4. Finally, a solution using a bit vector.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
  def is_palindrome_permutation(s1):
    bit_vector = create_bit_vector(s1) # assume ASCII string
    return bit_vector == 0 or check_exactly_one_bit_is_set(bit_vector)

  def create_bit_vector(s1):
    bit_vector = 0
    for char in s1:
      x = get_char_number(char)
      bit_vector = toggle(bit_vector, x)
    return bit_vector

  def check_exactly_one_bit_is_set(bit_vector):
    return (bit_vector & (bit_vector - 1)) == 0

  def toggle(bit_vector, index):
    if index < 0:
      return bit_vector

    mask = 1 << index

    if ((bit_vector & mask) == 0):
      bit_vector = bit_vector | mask # OR: set bit at "index" to 1
    else:
      bit_vector = bit_vector & (~mask) # XOR: toggle bit at "index" to 0 if 1 or 1 if 0

    return bit_vector

  def get_char_number(char):
    a = ord('a')
    z = ord('z')
    val = ord(char)

    if ((val >= a) and (val <= z)):
      return val - a

    return -1

  s1 = "tacocat"
  print(is_palindrome_permutation(s1))

Check out more useful information regarding bitmasking.

Written on December 21, 2018