Given a string s which consists of lowercase or uppercase letters, return the length of the longest palindrome that can be built with those letters. Letters are case sensitive, for example, “Aa” is not considered a palindrome.
Understand
給一個名為s的字串,由大小寫英文字母組成,要使用這些字母排列組合找出最長的回文長度。
題目把A與a視為不同字元
迴文:從最後一個字元到第一個字元的順序 剛好與 從第一個字元到最後一個字元順序 一致
因此,
當某種字母在字串中出現的頻率為偶數,則這些種類的字母可以放進迴文中。
當某種字母在字串中出現的頻率為奇數,則這些種類的字母只能從中抽取一種插入回文中,能放在正中央作為對稱點,如 aacccbb、AbcbA
Happy Case
| input | output | 解釋 |
|---|---|---|
| aabbcc | 6 | 可以組成aaccbb、bbaacc、ccbbaa |
Edge Case
| input | output | 解釋 |
|---|---|---|
| abc | 1 | 每種字元只出現一次,對稱點只能是a,b,c其中一種 |
| a | 1 | a |
| bb | 2 | |
| Aa | 1 | 只能取A或a作為迴文 |
Match
題目要考的是 Hash Map(字元頻率統計) + 貪心演算法
每種字元只做一次判斷,最終答案由每次的選擇堆疊而成,加上每次判斷皆互不影響,這是貪心演算法的特徵。
透過Hash Map把當下的字串中的所有字元出現頻率都統計出來,在為每種字元頻率值判斷奇偶數
不要嘗試所有組合
Plan
- 建立length儲存可能的迴文長度
- 建立旗杆判斷字串中有無奇數頻率的字元
- 使用字典
{}遍歷s字串的所有字元,取的當下每種字元的頻率值 - 遍歷字典中的key-value,並判斷value是偶數還是奇數
- 是奇數:步驟2的旗杆從false變成true,只把當下的字元頻率值-1 變成偶數值加入回文中,後續在遇到奇數,因為已經加過奇數頻率了(true)所以不在加入奇數頻率字元
- 是偶數:一律加入迴文中
- 檢查步驟2的旗杆是否為true
- 是true,則迴文長度 + 1 (因為步驟4-1已經加入奇數頻率值的部分)
- 是false,則迴文長度不便
Implement
def longestPalindrome(self, s: str) -> int:
freq = {}
odd_flag = false
for char in s:
freq[char] = freq.get(char, 0) + 1
length = 0
for value in freq.values():
if value % 2 == 0: #整除2
length += value
else:
if not odd_flag:
odd_flag = true
length = length + (value - 1)
if odd_flag:
length += 1
return length
dict.get(key, default) 解說
freq.get(char,0)
- 如果char不存在於freq字典中,則回傳預設值0
- 如果char存在於freq字典中,則回傳char在字典中的value
所以freq.get(char, 0) + 1代表 當char不存在於freq字典中,則把char寫入字典中且value設為1
Reviews
| input | output | 解釋 |
|---|---|---|
| aabbcc | 6 | 頻率為a:2 b:2 c:2,所以2+2+2 |
| abc | 1 | 頻率為a:1 b:1 c:1,所以(1-1)+1 |
| a | 1 | 頻率為a:1,所以(1-1)+1 |
| bb | 2 | 頻率為b:2,所以2 |
| Aa | 1 | 只能取A或a作為迴文 |
Evaluate
| 複雜度 | 值 | 說明 |
|---|---|---|
| 時間複雜度 | O(n) | 遍歷字串遍建立頻率統計表,在遍歷頻率統計表 |
| 空間複雜度 | O(1) | 字元種類最多 52 種(a-z + A-Z),Hash Map 大小為常數 |
如果要回傳迴文,則空間複雜度上升至O(n)