方法一:贪心 + 哈希表
思路与算法
根据回文串的定义,回文串可以由奇数或者偶数个 words 中的单词拼接而成,但必须满足以下条件:
如果数量为奇数,那么位于正中间的单词必须是回文字符串(即两个字符相等);
每个单词和反转后对应位置的单词必须互为反转字符串。
根据上面的两个条件,我们可以得出构造最长回文串的规则:
对于两个字符不同的单词,需要尽可能多的成对选择它和它的反转字符串(如有);
对于两个字符相同的单词,需要尽可能多的成对选择该单词;
如果按照上述条件挑选后,仍然存在未被选择的两个字符相同的单词(此时该字符串只可能有一个未被选择,且该字符串一定在 words 中出现奇数次),我们可以任意选择一个。
因此,我们用哈希表统计 words 中每个单词的出现次数。随后,我们遍历哈希表的所有元素,并用 res 维护可能构成回文字符串的最长长度,同时用初值为 false 的布尔变量 mid 判断是否存在可以作为中心单词的、出现奇数次的回文单词。在遍历到字符串 word 时,我们首先求出它反转后的字符串 rev,此时根据 word 与 rev 的关系,有以下两种情况:
word
=rev,此时我们需要统计两者在 words 出现次数的最小值,即为成对选择的最多数目。假设此时对数为 n,则其对最长回文字符串贡献的字符长度为 4n,我们将 res 加上对应值;
word=rev,此时可以构成的对数为 ⌊m/2⌋,即对最长回文字符串贡献的字符长度为 4⌊m/2⌋,我们同样将 res 加上对应值。除此以外,我们还需要判断 word 的出现次数 m 是否为奇数:
如果 m 为奇数,则存在可以作为中心单词的剩余回文单词,我们将 mid 置为 true;
如果 m 为偶数,则不存在可以作为中心单词的剩余回文单词,我们不改变 mid 的取值。
最后,我们根据 mid 的取值,判断最长回文串是否含有中心单词。如果 mid 为 true,则代表含有,我们将 res 加上 2;反之则没有,我们不进行任何操作。
最后,我们返回 res 作为最长回文串的长度。
细节
在遍历哈希表中的每个单词时,为了避免重复计算成对选择的单词,我们只在 word 的字典序大于等于 rev 时更新 res。
func longestPalindrome(words []string) int {
freq := make(map[string]int)
for _, word := range words {
freq[word]++
}
res := 0
mid := false
for word, cnt := range freq {
rev := string(word[1]) + string(word[0])
if word == rev {
if cnt%2 == 1 {
mid = true
}
res += 2 * (cnt / 2 * 2)
} else if strings.Compare(word, rev) > 0 {
res += 4 * min(freq[word], freq[rev])
}
}
if mid {
res += 2
}
return res
}