ctci/1. Arrays and strings/1.4. palindrome_permutation.md

3.7 KiB

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

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.

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.