哈希算法题目:最长回文串(构造最长回文串)
字数 1938 2025-12-18 17:23:02

哈希算法题目:最长回文串(构造最长回文串)

题目描述

给定一个包含大小写字母的字符串 s,你可以通过重新排列这些字母来构造一个最长的回文串。你需要返回这个最长回文串的长度。

注意:

  • 回文串是正着读和反着读都一样的字符串。
  • 构造时不需要使用所有字符,但只能使用给定的字符。
  • 字符串 s 只包含大小写字母,长度范围是 [1, 2000]。

示例 1:
输入: s = "abccccdd"
输出: 7
解释: 最长回文串可以是 "dccaccd"(或 "dccbccd" 等),长度为 7。

示例 2:
输入: s = "a"
输出: 1
解释: 最长回文串是 "a"。

解题思路详解

要构造最长回文串,关键是理解回文串的结构特性。一个回文串可以分为两部分:中心部分和对称部分。

  • 对称部分:字符成对出现(例如 "aa" 在两侧),数量可以是任意偶数。
  • 中心部分:在回文串正中心,可以有一个单独的字符(如果可用),这是回文串长度可以为奇数的原因。

因此,解题的核心是统计每个字符的出现次数,然后决定哪些字符可以成对使用,以及中心是否可以放一个单独的字符。

解题步骤

我们将通过一个例子 s = "abccccdd" 来逐步说明。

步骤1:统计字符频率

我们需要知道每个字符出现了多少次。由于 s 只包含大小写字母,我们可以用哈希表来记录频率。
遍历 s 的每个字符:

  • 遇到字符,在哈希表中增加计数。

对于 s = "abccccdd":

  • 字符频率:a:1, b:1, c:4, d:2。

哈希表(可用字典或长度为 256 的数组):

  • 初始化:freq = {}
  • 遍历 s:
    • 'a' -> freq['a'] = 1
    • 'b' -> freq['b'] = 1
    • 'c' -> freq['c'] = 4
    • 'c' -> freq['c'] = 5(实际是连续4个c,这里逐步加)
    • 'd' -> freq['d'] = 2

最终频率:a:1, b:1, c:4, d:2。

步骤2:计算可用的成对字符数

回文串的对称部分需要字符成对出现。
对于每个字符,其可用的对数为 freq[ch] // 2(整数除法)。
每对字符可以为回文串长度贡献 2。

遍历频率表:

  • a: 1 // 2 = 0 对
  • b: 1 // 2 = 0 对
  • c: 4 // 2 = 2 对 -> 贡献长度 4
  • d: 2 // 2 = 1 对 -> 贡献长度 2

当前总长度贡献 = 4 + 2 = 6。

步骤3:检查是否可以添加一个中心字符

如果任何字符有剩余(即出现次数是奇数),我们可以取一个字符放在中心,使回文串长度增加 1。
注意:中心只能放一个字符,即使多个字符有剩余,也只能用一个。

检查每个字符的奇偶性:

  • a: 1(奇数)-> 有剩余
  • b: 1(奇数)-> 有剩余
  • c: 4(偶数)-> 无剩余
  • d: 2(偶数)-> 无剩余

存在奇数频率字符(a 或 b),所以可以添加一个中心字符。
总长度 = 对称部分长度 + 1 = 6 + 1 = 7。

步骤4:整合算法

我们可以遍历一次字符串统计频率,然后再遍历频率表(或直接在统计时处理)来累加成对字符数和检查奇数标志。

伪代码:

初始化:length = 0, hasOdd = False
创建一个数组或字典 freq 记录每个字符出现次数
遍历字符串 s 的每个字符 ch:
    freq[ch] += 1
遍历 freq 中的每个计数 count:
    length += (count // 2) * 2
    if count % 2 == 1:
        hasOdd = True
如果 hasOdd 为 True:
    length += 1
返回 length

步骤5:优化空间和时间

我们可以一次遍历同时累加长度和标记奇数。
思路:使用一个集合(哈希集合)来跟踪出现奇数次的字符。

  • 遍历每个字符,如果它不在集合中,就加入集合(表示出现奇数次);如果已在集合中,就移除(表示出现偶数次)。
  • 遍历结束后,集合中剩下的字符就是出现奇数次的字符。
  • 对称部分长度 = 总字符数 - 集合大小 + 是否用中心。

更直接的算法:

oddSet = set()
for ch in s:
    if ch in oddSet:
        oddSet.remove(ch)
    else:
        oddSet.add(ch)
if len(oddSet) > 0:
    return len(s) - len(oddSet) + 1
else:
    return len(s)

解释:

  • 每次字符成对出现时,会从集合中移除,因此集合中保存的是出现奇数次的字符。
  • 最长回文串长度 = 总字符数 - 奇数字符数 + 1(如果奇数字符数 > 0,可以选一个放中心)。
  • 如果奇数字符数 = 0,则全部字符都可以成对使用,长度就是总字符数。

示例 s = "abccccdd":

  • 总字符数 = 8。
  • 遍历后 oddSet = {'a', 'b'}(因为 c 和 d 都成对移除了)。
  • len(oddSet) = 2。
  • 长度 = 8 - 2 + 1 = 7。

复杂度分析

  • 时间复杂度:O(n),其中 n 是字符串长度,只需遍历字符串一次。
  • 空间复杂度:O(1),因为字符集大小是常数(最多 52 个大小写字母,集合大小有限)。

扩展思考

  • 如果题目要求输出一个具体的最长回文串(而不仅仅是长度),可以在统计频率后,构造前半部分和中心,然后反向得到完整回文串。
  • 这题是哈希表在计数问题中的经典应用,通过统计频率来利用回文串的对称性质。
哈希算法题目:最长回文串(构造最长回文串) 题目描述 给定一个包含大小写字母的字符串 s,你可以通过重新排列这些字母来构造一个最长的回文串。你需要返回这个最长回文串的长度。 注意: 回文串是正着读和反着读都一样的字符串。 构造时不需要使用所有字符,但只能使用给定的字符。 字符串 s 只包含大小写字母,长度范围是 [ 1, 2000 ]。 示例 1: 输入: s = "abccccdd" 输出: 7 解释: 最长回文串可以是 "dccaccd"(或 "dccbccd" 等),长度为 7。 示例 2: 输入: s = "a" 输出: 1 解释: 最长回文串是 "a"。 解题思路详解 要构造最长回文串,关键是理解回文串的结构特性。一个回文串可以分为两部分:中心部分和对称部分。 对称部分:字符成对出现(例如 "aa" 在两侧),数量可以是任意偶数。 中心部分:在回文串正中心,可以有一个单独的字符(如果可用),这是回文串长度可以为奇数的原因。 因此,解题的核心是统计每个字符的出现次数,然后决定哪些字符可以成对使用,以及中心是否可以放一个单独的字符。 解题步骤 我们将通过一个例子 s = "abccccdd" 来逐步说明。 步骤1:统计字符频率 我们需要知道每个字符出现了多少次。由于 s 只包含大小写字母,我们可以用哈希表来记录频率。 遍历 s 的每个字符: 遇到字符,在哈希表中增加计数。 对于 s = "abccccdd": 字符频率:a:1, b:1, c:4, d:2。 哈希表(可用字典或长度为 256 的数组): 初始化: freq = {} 。 遍历 s: 'a' -> freq[ 'a' ] = 1 'b' -> freq[ 'b' ] = 1 'c' -> freq[ 'c' ] = 4 'c' -> freq[ 'c' ] = 5(实际是连续4个c,这里逐步加) 'd' -> freq[ 'd' ] = 2 最终频率:a:1, b:1, c:4, d:2。 步骤2:计算可用的成对字符数 回文串的对称部分需要字符成对出现。 对于每个字符,其可用的对数为 freq[ch] // 2 (整数除法)。 每对字符可以为回文串长度贡献 2。 遍历频率表: a: 1 // 2 = 0 对 b: 1 // 2 = 0 对 c: 4 // 2 = 2 对 -> 贡献长度 4 d: 2 // 2 = 1 对 -> 贡献长度 2 当前总长度贡献 = 4 + 2 = 6。 步骤3:检查是否可以添加一个中心字符 如果任何字符有剩余(即出现次数是奇数),我们可以取一个字符放在中心,使回文串长度增加 1。 注意:中心只能放一个字符,即使多个字符有剩余,也只能用一个。 检查每个字符的奇偶性: a: 1(奇数)-> 有剩余 b: 1(奇数)-> 有剩余 c: 4(偶数)-> 无剩余 d: 2(偶数)-> 无剩余 存在奇数频率字符(a 或 b),所以可以添加一个中心字符。 总长度 = 对称部分长度 + 1 = 6 + 1 = 7。 步骤4:整合算法 我们可以遍历一次字符串统计频率,然后再遍历频率表(或直接在统计时处理)来累加成对字符数和检查奇数标志。 伪代码: 步骤5:优化空间和时间 我们可以一次遍历同时累加长度和标记奇数。 思路:使用一个集合(哈希集合)来跟踪出现奇数次的字符。 遍历每个字符,如果它不在集合中,就加入集合(表示出现奇数次);如果已在集合中,就移除(表示出现偶数次)。 遍历结束后,集合中剩下的字符就是出现奇数次的字符。 对称部分长度 = 总字符数 - 集合大小 + 是否用中心。 更直接的算法: 解释: 每次字符成对出现时,会从集合中移除,因此集合中保存的是出现奇数次的字符。 最长回文串长度 = 总字符数 - 奇数字符数 + 1(如果奇数字符数 > 0,可以选一个放中心)。 如果奇数字符数 = 0,则全部字符都可以成对使用,长度就是总字符数。 示例 s = "abccccdd": 总字符数 = 8。 遍历后 oddSet = {'a', 'b'}(因为 c 和 d 都成对移除了)。 len(oddSet) = 2。 长度 = 8 - 2 + 1 = 7。 复杂度分析 时间复杂度:O(n),其中 n 是字符串长度,只需遍历字符串一次。 空间复杂度:O(1),因为字符集大小是常数(最多 52 个大小写字母,集合大小有限)。 扩展思考 如果题目要求输出一个具体的最长回文串(而不仅仅是长度),可以在统计频率后,构造前半部分和中心,然后反向得到完整回文串。 这题是哈希表在计数问题中的经典应用,通过统计频率来利用回文串的对称性质。