Facebook Pixel

266. Palindrome Permutation 🔒

EasyBit ManipulationHash TableString
Leetcode Link

Problem Description

Given a string s, you need to determine if any rearrangement (permutation) of the characters in the string can form a palindrome. Return true if it's possible to rearrange the characters to create a palindrome, and false otherwise.

A palindrome is a string that reads the same forward and backward. For example, "racecar" and "noon" are palindromes.

The key insight is that for a string to be rearrangeable into a palindrome:

  • If the string has even length, every character must appear an even number of times
  • If the string has odd length, exactly one character can appear an odd number of times (this will be the middle character), while all others must appear an even number of times

For example:

  • "aab" can form "aba" which is a palindrome, so return true
  • "code" cannot form any palindrome arrangement because we have 4 different characters each appearing once, so return false

The solution counts the frequency of each character using Counter(s), then checks how many characters have odd frequencies. If at most one character has an odd frequency count, the string can be rearranged into a palindrome.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

To understand if we can rearrange characters to form a palindrome, let's think about the structure of palindromes themselves.

In any palindrome, characters are mirrored around the center. This means:

  • Characters on the left half must have matching characters on the right half
  • Each character (except possibly one in the middle) must be pairable

Consider "racecar" - we have pairs: r-r, a-a, c-c, and one e in the middle. Notice how every character except the middle one comes in pairs.

This leads us to a crucial observation: for a string to be rearrangeable into a palindrome, we need to be able to pair up most (or all) of the characters.

When we count character frequencies:

  • Characters with even counts can always be perfectly paired and distributed symmetrically
  • Characters with odd counts have one "leftover" character after pairing

If we have multiple characters with odd counts, we'd have multiple "leftover" characters with nowhere to place them symmetrically. However, a palindrome can accommodate at most ONE unpaired character (in the center position).

Therefore, the condition becomes simple: count how many characters have odd frequencies. If this count is 0 or 1, we can form a palindrome. If it's 2 or more, we cannot.

The expression v & 1 cleverly checks if a number is odd (returns 1 if odd, 0 if even), and summing these gives us the total count of characters with odd frequencies. The condition < 2 ensures we have at most one character with an odd count.

Solution Approach

The solution uses a counting approach to determine if the string can be rearranged into a palindrome.

Step 1: Count Character Frequencies

We use Python's Counter from the collections module to count the occurrences of each character in the string:

Counter(s)

This creates a dictionary-like object where keys are characters and values are their frequencies.

Step 2: Check Odd Frequency Count

For each character's frequency count v, we check if it's odd using the bitwise AND operation:

v & 1

This operation returns:

  • 1 if v is odd (because odd numbers have their least significant bit set to 1)
  • 0 if v is even (because even numbers have their least significant bit set to 0)

Step 3: Sum and Validate

We sum up all the results from the bitwise operation:

sum(v & 1 for v in Counter(s).values())

This gives us the total number of characters that appear an odd number of times.

Step 4: Final Check

The condition < 2 ensures that at most one character has an odd frequency count. If the sum is 0 or 1, the function returns True (palindrome possible). If the sum is 2 or greater, it returns False (palindrome impossible).

Time Complexity: O(n) where n is the length of the string, as we need to iterate through all characters once to count them.

Space Complexity: O(k) where k is the number of unique characters in the string, needed to store the frequency counts.

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through the solution with the string s = "aabbcc":

Step 1: Count Character Frequencies

  • Using Counter(s), we get: {'a': 2, 'b': 2, 'c': 2}
  • Each character appears exactly 2 times (even count)

Step 2: Check Odd Frequencies

  • For 'a': 2 & 1 = 0 (2 in binary is 10, AND with 01 gives 00 = 0)
  • For 'b': 2 & 1 = 0
  • For 'c': 2 & 1 = 0

Step 3: Sum the Odd Counts

  • Sum = 0 + 0 + 0 = 0

Step 4: Final Check

  • Since 0 < 2 is True, we can form a palindrome
  • Indeed, we can rearrange to form "abccba" or "cabacb"

Now let's try s = "aabbcd":

Step 1: Count Character Frequencies

  • Using Counter(s), we get: {'a': 2, 'b': 2, 'c': 1, 'd': 1}
  • 'a' and 'b' appear 2 times (even), 'c' and 'd' appear 1 time (odd)

Step 2: Check Odd Frequencies

  • For 'a': 2 & 1 = 0
  • For 'b': 2 & 1 = 0
  • For 'c': 1 & 1 = 1 (1 in binary is 01, AND with 01 gives 01 = 1)
  • For 'd': 1 & 1 = 1

Step 3: Sum the Odd Counts

  • Sum = 0 + 0 + 1 + 1 = 2

Step 4: Final Check

  • Since 2 < 2 is False, we cannot form a palindrome
  • With two characters having odd counts, we'd have two leftover characters that cannot be placed symmetrically

Solution Implementation

1from collections import Counter
2
3class Solution:
4    def canPermutePalindrome(self, s: str) -> bool:
5        """
6        Determine if a string can be rearranged to form a palindrome.
7      
8        A string can form a palindrome if:
9        - At most one character has an odd frequency (for odd-length strings)
10        - All characters have even frequencies (for even-length strings)
11      
12        Args:
13            s: Input string to check
14          
15        Returns:
16            True if the string can be rearranged into a palindrome, False otherwise
17        """
18        # Count the frequency of each character in the string
19        char_frequency = Counter(s)
20      
21        # Count how many characters have odd frequencies
22        # Using bitwise AND with 1 to check if frequency is odd
23        odd_frequency_count = sum(freq & 1 for freq in char_frequency.values())
24      
25        # A valid palindrome can have at most 1 character with odd frequency
26        # (the middle character in odd-length palindromes)
27        return odd_frequency_count < 2
28
1class Solution {
2    public boolean canPermutePalindrome(String s) {
3        // Array to store frequency count of each lowercase letter (a-z)
4        int[] characterCount = new int[26];
5      
6        // Count the frequency of each character in the string
7        for (char character : s.toCharArray()) {
8            // Map character to array index (0-25) by subtracting 'a'
9            characterCount[character - 'a']++;
10        }
11      
12        // Count how many characters appear an odd number of times
13        int oddFrequencyCount = 0;
14        for (int frequency : characterCount) {
15            // Use bitwise AND with 1 to check if frequency is odd
16            // If frequency is odd, (frequency & 1) equals 1, otherwise 0
17            oddFrequencyCount += frequency & 1;
18        }
19      
20        // A string can form a palindrome if at most one character has odd frequency
21        // - If string length is even: all characters must have even frequency (oddFrequencyCount = 0)
22        // - If string length is odd: exactly one character must have odd frequency (oddFrequencyCount = 1)
23        return oddFrequencyCount < 2;
24    }
25}
26
1class Solution {
2public:
3    bool canPermutePalindrome(string s) {
4        // Array to count frequency of each lowercase letter (assuming input contains only 'a'-'z')
5        vector<int> charFrequency(26);
6      
7        // Count the frequency of each character in the string
8        for (char& c : s) {
9            ++charFrequency[c - 'a'];
10        }
11      
12        // Count how many characters have odd frequency
13        int oddFrequencyCount = 0;
14        for (int frequency : charFrequency) {
15            // Use bitwise AND to check if frequency is odd (last bit is 1 for odd numbers)
16            oddFrequencyCount += frequency & 1;
17        }
18      
19        // A string can form a palindrome if at most one character has odd frequency
20        // - Even length palindrome: all characters must have even frequency
21        // - Odd length palindrome: exactly one character can have odd frequency (the middle one)
22        return oddFrequencyCount < 2;
23    }
24};
25
1/**
2 * Determines if a string can be rearranged to form a palindrome
3 * @param s - The input string containing only lowercase letters
4 * @returns true if the string can form a palindrome, false otherwise
5 */
6function canPermutePalindrome(s: string): boolean {
7    // Initialize frequency counter array for 26 lowercase letters (a-z)
8    const charFrequency: number[] = Array(26).fill(0);
9  
10    // Count the frequency of each character in the string
11    for (const char of s) {
12        // Convert character to index (0-25) by subtracting ASCII value of 'a' (97)
13        const index: number = char.charCodeAt(0) - 97;
14        charFrequency[index]++;
15    }
16  
17    // Count characters with odd frequency
18    // For a palindrome, at most one character can have odd frequency
19    const oddFrequencyCount: number = charFrequency.filter(count => count % 2 === 1).length;
20  
21    // Return true if there's at most one character with odd frequency
22    return oddFrequencyCount < 2;
23}
24

Time and Space Complexity

Time Complexity: O(n), where n is the length of the string s. The Counter(s) operation iterates through each character in the string once to count occurrences, which takes O(n) time. Then, iterating through the values of the counter dictionary takes O(|Σ|) time where |Σ| is the size of the character set. Since O(n) + O(|Σ|) and typically n ≥ |Σ| for meaningful inputs, the overall time complexity is O(n).

Space Complexity: O(|Σ|), where |Σ| is the size of the character set. The Counter object stores at most |Σ| unique characters and their counts. If the string contains only lowercase letters, then |Σ| = 26. If it can contain any ASCII characters, then |Σ| could be up to 128 or 256 depending on the character encoding.

Common Pitfalls

1. Misunderstanding the Problem Requirements

A common mistake is thinking you need to actually generate or check all possible permutations of the string to see if any form a palindrome. This would lead to an inefficient O(n! × n) solution:

# INCORRECT: Checking all permutations (very inefficient!)
from itertools import permutations

def canPermutePalindrome_wrong(s: str) -> bool:
    for perm in permutations(s):
        perm_str = ''.join(perm)
        if perm_str == perm_str[::-1]:  # Check if palindrome
            return True
    return False

Solution: Focus on the mathematical property of palindromes - character frequency patterns determine if a palindrome is possible, not the actual arrangements.

2. Incorrect Odd Count Logic

Developers might incorrectly implement the odd frequency check using modulo operator without proper consideration:

# PITFALL: Using wrong comparison
def canPermutePalindrome_pitfall(s: str) -> bool:
    char_frequency = Counter(s)
    odd_count = 0
    for freq in char_frequency.values():
        if freq % 2 == 1:  # This works but...
            odd_count += 1
    return odd_count <= 1  # Easy to mistakenly write == 1

Solution: The condition should be <= 1 or < 2, not == 1, because even-length palindromes have zero characters with odd frequencies.

3. Not Handling Edge Cases

Forgetting to consider empty strings or single characters:

# PITFALL: May not handle edge cases properly
def canPermutePalindrome_edge(s: str) -> bool:
    if not s:  # Empty string handling might be missed
        return True
    # ... rest of logic

Solution: The provided solution naturally handles these cases:

  • Empty string: Counter returns empty dict, sum is 0, returns True
  • Single character: Counter returns {char: 1}, sum is 1, returns True

4. Using Sets Instead of Counter

Some might try to use a set to track odd/even frequencies, toggling elements:

# Alternative approach but prone to errors
def canPermutePalindrome_set(s: str) -> bool:
    odd_chars = set()
    for char in s:
        if char in odd_chars:
            odd_chars.remove(char)  # Even occurrence
        else:
            odd_chars.add(char)      # Odd occurrence
    return len(odd_chars) <= 1

While this works, it's less intuitive and harder to debug than using Counter.

5. Overthinking Bitwise Operations

The v & 1 operation might confuse developers who aren't familiar with bitwise operations:

# Some might write unnecessarily complex checks
if v & 1 == 1:  # Redundant comparison
    odd_count += 1

# Or use less efficient operations
if v % 2 != 0:  # Works but slightly less efficient than bitwise
    odd_count += 1

Solution: Remember that v & 1 directly gives 1 for odd and 0 for even numbers, making it perfect for summing directly.

Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

Which data structure is used to implement recursion?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More