266. Palindrome Permutation 🔒
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 returntrue
"code"
cannot form any palindrome arrangement because we have 4 different characters each appearing once, so returnfalse
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.
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
ifv
is odd (because odd numbers have their least significant bit set to 1)0
ifv
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 EvaluatorExample 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 is10
, AND with01
gives00
= 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
isTrue
, 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 is01
, AND with01
gives01
= 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
isFalse
, 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.
Which data structure is used to implement recursion?
Recommended Readings
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!