409. Longest Palindrome
Problem Description
You are given a string s
that consists of lowercase or uppercase letters. Your task is to find the length of the longest palindrome that can be built using those letters.
A palindrome is a string that reads the same forwards and backwards. When building the palindrome, you can rearrange the letters from the input string in any order, but you can only use each letter the number of times it appears in the original string.
Note that letters are case-sensitive, meaning 'A'
and 'a'
are considered different characters. For example, "Aa"
is not a valid palindrome because the two characters are different.
The key insight is that in a palindrome:
- Characters that appear in pairs can be placed symmetrically (one on each side)
- At most one character can appear an odd number of times (it would go in the center)
For example, if s = "abccccdd"
, you can build palindromes like "dccaccd"
or "ccdadcc"
with length 7. The character 'a'
or 'b'
(which appear once) can be placed in the center, while the other characters form symmetric pairs.
The solution counts the occurrences of each character, uses as many pairs as possible (by taking v // 2 * 2
for each count v
), and adds 1 to the result if there's any character with an odd count that can be placed in the center.
Intuition
To build the longest palindrome, we need to understand the structure of palindromes. A palindrome reads the same forwards and backwards, which means characters must appear in matching pairs on both sides of the center.
Let's think about what makes a valid palindrome:
- Even-length palindrome: Every character appears an even number of times (e.g.,
"abba"
,"aabbaa"
) - Odd-length palindrome: Exactly one character appears an odd number of times in the center, while all others appear in pairs (e.g.,
"aba"
,"racecar"
)
This leads us to a greedy strategy: we want to use as many characters as possible in pairs. For each character that appears n
times:
- If
n
is even, we can use alln
occurrences (placingn/2
on each side) - If
n
is odd, we can usen-1
occurrences as pairs (placing(n-1)/2
on each side)
The expression v // 2 * 2
elegantly captures this logic - it gives us the largest even number less than or equal to v
. For example:
- If
v = 4
:4 // 2 * 2 = 2 * 2 = 4
(use all 4) - If
v = 5
:5 // 2 * 2 = 2 * 2 = 4
(use 4 out of 5)
After using all possible pairs, we check if there's any leftover character (when ans < len(s)
). If there is, we can place exactly one character in the center of our palindrome, increasing our answer by 1. This works because a palindrome can have at most one character in its center position.
Solution Approach
The implementation uses a counting approach with a hash table to track character frequencies.
Step 1: Count Character Frequencies
We use Python's Counter
from the collections module to count occurrences of each character in the string s
. This creates a hash table cnt
where keys are characters and values are their frequencies.
Step 2: Calculate Pairs
We iterate through all frequency values in cnt.values()
. For each frequency v
:
- Calculate
v // 2 * 2
to get the maximum even number โคv
- This represents how many of that character we can use in pairs
- Sum all these values to get the total length from paired characters
The expression sum(v // 2 * 2 for v in cnt.values())
efficiently computes this in one line.
Step 3: Add Center Character (if possible)
After using all possible pairs, we check if we can add one more character to the center:
- If
ans < len(s)
, it means we have at least one character with an odd count that wasn't fully used - We can place one such character in the center, so we add 1 to our answer
- The expression
int(ans < len(s))
converts the boolean to 1 if true, 0 if false
Example Walkthrough:
For s = "abccccdd"
:
- Count:
{'a': 1, 'b': 1, 'c': 4, 'd': 2}
- Pairs:
1//2*2 + 1//2*2 + 4//2*2 + 2//2*2 = 0 + 0 + 4 + 2 = 6
- Since
6 < 8
, we can add 1 for a center character - Final answer:
6 + 1 = 7
The time complexity is O(n)
for counting characters, and space complexity is O(k)
where k
is the number of unique characters.
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 s = "aaBBccc"
:
Step 1: Count Character Frequencies
'a'
appears 2 times'B'
appears 2 times'c'
appears 3 times
Step 2: Calculate Pairs
For each character, we determine how many can be used in pairs using v // 2 * 2
:
'a'
:2 // 2 * 2 = 1 * 2 = 2
โ Use all 2 characters'B'
:2 // 2 * 2 = 1 * 2 = 2
โ Use all 2 characters'c'
:3 // 2 * 2 = 1 * 2 = 2
โ Use 2 out of 3 characters
Total from pairs: 2 + 2 + 2 = 6
Step 3: Check for Center Character
- Current palindrome length: 6
- Original string length: 7
- Since
6 < 7
, we have leftover characters (the unused 'c') - We can place one character in the center:
6 + 1 = 7
Final Answer: 7
We could build palindromes like:
"aBccBa"
with the extra 'c' in the center"cBaaBc"
with the extra 'c' in the center"acBBca"
with the extra 'c' in the center
The key insight: we used all even counts completely, used the even portion of odd counts, and added 1 for a center character since we had leftovers.
Solution Implementation
1from collections import Counter
2
3class Solution:
4 def longestPalindrome(self, s: str) -> int:
5 # Count frequency of each character in the string
6 char_count = Counter(s)
7
8 # Calculate the length of the longest palindrome
9 # For each character, we can use pairs (even count) in the palindrome
10 palindrome_length = sum(count // 2 * 2 for count in char_count.values())
11
12 # If palindrome length is less than string length, we can add one odd character in the middle
13 # This checks if there's at least one character with odd frequency
14 if palindrome_length < len(s):
15 palindrome_length += 1
16
17 return palindrome_length
18
1class Solution {
2 public int longestPalindrome(String s) {
3 // Array to store frequency of each character (ASCII range 0-127)
4 int[] charFrequency = new int[128];
5 int stringLength = s.length();
6
7 // Count frequency of each character in the string
8 for (int i = 0; i < stringLength; i++) {
9 charFrequency[s.charAt(i)]++;
10 }
11
12 // Calculate the length of longest palindrome
13 int palindromeLength = 0;
14
15 // For each character, we can use pairs of it in the palindrome
16 // If a character appears 5 times, we can use 4 of them (2 pairs)
17 for (int frequency : charFrequency) {
18 // Add the largest even number less than or equal to frequency
19 palindromeLength += (frequency / 2) * 2;
20 }
21
22 // If palindrome length is less than original string length,
23 // it means we have at least one character with odd frequency
24 // We can place one such character in the middle of palindrome
25 if (palindromeLength < stringLength) {
26 palindromeLength++;
27 }
28
29 return palindromeLength;
30 }
31}
32
1class Solution {
2public:
3 int longestPalindrome(string s) {
4 // Array to store frequency of each ASCII character (128 possible values)
5 int charFrequency[128] = {0};
6
7 // Count the frequency of each character in the string
8 for (char c : s) {
9 ++charFrequency[c];
10 }
11
12 // Calculate the length of the longest palindrome
13 int palindromeLength = 0;
14
15 // For each character, we can use pairs (even count) in the palindrome
16 // If a character appears 5 times, we can use 4 of them (2 pairs)
17 for (int frequency : charFrequency) {
18 palindromeLength += (frequency / 2) * 2;
19 }
20
21 // If we haven't used all characters, we can add one more character in the middle
22 // This happens when there's at least one character with odd frequency
23 if (palindromeLength < s.size()) {
24 palindromeLength += 1;
25 }
26
27 return palindromeLength;
28 }
29};
30
1/**
2 * Calculates the length of the longest palindrome that can be built with the given string characters
3 * @param s - Input string containing characters to build the palindrome
4 * @returns The length of the longest possible palindrome
5 */
6function longestPalindrome(s: string): number {
7 // Create a frequency map to count occurrences of each character
8 const characterFrequency: Record<string, number> = {};
9
10 // Count the frequency of each character in the string
11 for (const character of s) {
12 characterFrequency[character] = (characterFrequency[character] || 0) + 1;
13 }
14
15 // Calculate the length of the palindrome by taking pairs of characters
16 // For each character, we can use floor(count/2) * 2 characters in the palindrome
17 let palindromeLength = Object.values(characterFrequency).reduce(
18 (accumulator, frequency) => accumulator + Math.floor(frequency / 2) * 2,
19 0
20 );
21
22 // If there are any characters left (palindrome length < string length),
23 // we can place one odd character in the middle of the palindrome
24 palindromeLength += palindromeLength < s.length ? 1 : 0;
25
26 return palindromeLength;
27}
28
Time and Space Complexity
The time complexity is O(n + |ฮฃ|)
, where n
is the length of the string s
and |ฮฃ|
is the size of the character set. The Counter(s)
operation takes O(n)
time to count all characters in the string. The subsequent iteration through cnt.values()
takes O(|ฮฃ|)
time in the worst case when all possible characters appear in the string. Since the character set in this problem consists of ASCII characters, |ฮฃ| = 128
.
The space complexity is O(|ฮฃ|)
. The Counter
object stores at most |ฮฃ|
unique characters and their frequencies, which requires O(|ฮฃ|)
space. In this problem, |ฮฃ| = 128
for the ASCII character set.
Common Pitfalls
Pitfall 1: Misunderstanding the Center Character Logic
The Mistake: Many developers incorrectly try to count how many characters have odd frequencies and add that count to the result, thinking multiple odd-count characters can be used.
# INCORRECT approach
def longestPalindrome(self, s: str) -> int:
char_count = Counter(s)
palindrome_length = 0
odd_count = 0
for count in char_count.values():
palindrome_length += count // 2 * 2
if count % 2 == 1:
odd_count += 1
# Wrong: Adding all odd counts
return palindrome_length + odd_count
Why It's Wrong: In a palindrome, only ONE character can have an odd count (placed in the center). The above code would incorrectly return 8 for "aabbcc" instead of 6.
The Fix: Add at most 1 to the result if ANY character has an odd count:
# CORRECT approach
if palindrome_length < len(s):
palindrome_length += 1
Pitfall 2: Incorrectly Calculating Usable Pairs
The Mistake: Using only complete pairs and discarding the remainder entirely:
# INCORRECT approach
def longestPalindrome(self, s: str) -> int:
char_count = Counter(s)
palindrome_length = 0
for count in char_count.values():
palindrome_length += (count // 2) * 2 # Looks correct but...
# Forgetting to check for center character
return palindrome_length # Missing the +1 for odd counts
Why It's Wrong: This approach forgets that if we have leftover characters after pairing, we can still use ONE of them as the center of the palindrome.
The Fix: Always check if the total paired length is less than the original string length:
palindrome_length = sum(count // 2 * 2 for count in char_count.values())
if palindrome_length < len(s): # This check is crucial
palindrome_length += 1
Pitfall 3: Over-complicating the Odd Count Check
The Mistake: Explicitly tracking whether any character has an odd count with additional variables:
# OVERLY COMPLEX approach
def longestPalindrome(self, s: str) -> int:
char_count = Counter(s)
palindrome_length = 0
has_odd = False
for count in char_count.values():
palindrome_length += count // 2 * 2
if count % 2 == 1:
has_odd = True
return palindrome_length + (1 if has_odd else 0)
Why It's Inefficient: While this works, it uses unnecessary variables and explicit modulo operations when a simpler comparison suffices.
The Fix:
The condition palindrome_length < len(s)
elegantly captures whether any odd-count character exists:
# Elegant one-liner after calculating pairs
return palindrome_length + int(palindrome_length < len(s))
This works because if all characters were used in pairs, palindrome_length
would equal len(s)
. Any difference means odd-count characters exist.
Consider the classic dynamic programming of fibonacci numbers, what is the recurrence relation?
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
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
Want a Structured Path to Master System Design Too? Donโt Miss This!