Skip to content
📖预计阅读时长:0 分钟字数:0

代码随想录 — 哈希与字符串

对应 leetcode-master:哈希表理论基础、242/349/202/1/454/383、15/18、344/541/KMP/151/459 等。


一、哈希表理论基础(何时用、怎么选结构)

  • 作用:根据关键码直接访问,快速判断一个元素是否在集合里(O(1) 查询),典型用法是「有没有出现过」「出现几次」。
  • 实现:哈希函数把 key 映射到下标;哈希碰撞用拉链法(冲突的放链表)或线性探测(往后找空位)。一般用语言内置的哈希表即可。
  • 常见三种结构
    • 数组:key 是连续整数或小范围(如 26 个字母)时,用数组当哈希表,下标即 key。
    • Set:只关心「有没有」,不关心次数或下标时用 Set(如去重、是否存在)。
    • Map:需要 key → value(如「值 → 下标」「值 → 次数」)时用 Map。

何时想到哈希:题目里出现「判断是否出现过」「找配对」「统计次数」等,优先考虑哈希,用空间换时间


二、两数之和(1)— 为什么用 Map

题意:在数组中找两个数之和为 target,返回两数的下标

为什么不用 Set:不仅要判断「target - 当前数」是否存在,还要知道它的下标,所以需要 key = 数值,value = 下标,用 Map。

思路:遍历数组,对当前数 nums[i],在 Map 里查是否存在 target - nums[i];若有则返回 [map.get(target - nums[i]), i];若无则把 (nums[i], i) 放入 Map(表示「值为 nums[i] 的下标是 i」)。这样每个数只遍历一次,且用到的是「之前已经遍历过的数」的下标。

易错:Map 里存的是「已经遍历过的数 → 其下标」,查的是「与当前数配对的数」是否出现过,别把 key/value 搞反。


三、哈希表其它经典题(思路一句话)

  • 242 有效的字母异位词:两串长度相同且各字符出现次数相同。用长度 26 的数组当哈希表,统计第一串各字母次数,再减第二串,若全 0 则是异位词。
  • 349 两个数组的交集:去重 + 判断存在。用 Set 存一个数组,遍历另一个,在 Set 中的加入结果并可从 Set 删掉避免重复。
  • 202 快乐数:按规则不断求平方和,若出现 1 则快乐;若出现重复则进入环,不快乐。用 Set 记录出现过的数,重复即 false。
  • 454 四数相加 II:四个数组各取一个数求和为 0。可把前两数组的两两之和存 Map(和 → 出现次数),再枚举后两数组的两两之和,查 Map 中是否有相反数,累加次数。
  • 383 赎金信:判断 magazine 的字符能否组成 ransomNote。用数组或 Map 统计 magazine 各字符数量,再减 ransomNote 的消耗,若有字符不够则 false。
  • 15 三数之和:找所有和为 0 的三元组且不重复。排序 + 双指针更合适;固定一个数,在右侧用左右指针找两数之和为负的固定数。注意去重(固定数、左、右都要跳过重复)。
  • 18 四数之和:同思路,固定两个数再双指针;同样注意去重和剪枝。

小结:需要「值 → 下标」用 Map;只 need「有没有」且 key 范围小用数组,否则用 Set;若还要去重或有序,再考虑 set/map 的有序版本。


四、字符串 — 反转与空格(344 / 541 / 151)

  • 344 反转字符串:原地反转,左右指针交换即可。
  • 541 反转字符串 II:每 2k 个字符为一组,每组内只反转前 k 个。按题意模拟,注意最后一组可能不足 2k。
  • 151 翻转字符串里的单词:先整体反转,再对每个单词内部反转;或先按空格 split 再 reverse 再 join。若要求 O(1) 空间,需在原串上先去掉多余空格再按「整体反转 + 逐词反转」做。

五、KMP 与 重复子串(28 实现 strStr / 459)

KMP 核心:当模式串与主串在某位失配时,不回溯主串指针,而是根据已匹配部分利用 next 数组(最长相等前后缀长度)把模式串指针回退到合适位置,继续比较。

  • next 数组next[i] 表示模式串 [0, i-1] 这段子串的「最长相等真前后缀」长度。求 next 的过程本身也是一个「模式串与自身」的匹配,用双指针或递推实现。
  • 28 实现 strStr:主串中找模式串首次出现位置,用 KMP 或暴力均可;KMP 时间 O(n+m)。

459 重复的子字符串:判断 s 是否由某子串重复多次构成。思路一:s + s 拼起来,去掉首尾各一个字符后,若还能找到 s,则是重复子串。思路二:用 KMP 的 next 数组,若 len % (len - next[len-1]) === 0next[len-1] !== 0,则说明有重复周期。

javascript
 // KMP实现 strStr
   function strStr(haystack, needle) {
     if (needle === "") return 0;
     const n = needle.length;
     const lps = Array(n).fill(0);

     // build LPS
     for (let i = 1, len = 0; i < n; ) {
       if (needle[i] === needle[len]) {
         lps[i++] = ++len;
       } else if (len > 0) {
         len = lps[len - 1];
       } else {
         lps[i++] = 0;
       }
     }

     // search
     for (let i = 0, j = 0; i < haystack.length; ) {
       if (haystack[i] === needle[j]) {
         i++; j++;
         if (j === n) return i - j;
       } else if (j > 0) {
         j = lps[j - 1];
       } else {
         i++;
       }
     }
     return -1;
   }

六、字符串总结

类型方法
反转双指针交换;或 split/reverse/join(非 O(1) 空间)
匹配 / 子串KMP(next 数组、失配回退);重复子串可结合 next 的周期性质
哈希统计字符用数组(26/128);需要 key-value 用 Map

建议:两数之和的「为什么用 Map、key/value 存什么」要能说清;KMP 至少理解「next 是什么、怎么用」,能写出求 next 和匹配主流程即可。


附:核心代码模板

两数之和(Map)

javascript
// JavaScript — 时间 O(n),空间 O(n)
function twoSum(nums, target) {
  const map = new Map();
  for (let i = 0; i < nums.length; i++) {
    const complement = target - nums[i];
    if (map.has(complement)) return [map.get(complement), i];
    map.set(nums[i], i);
  }
  return [];
}

有效的字母异位词(数组哈希)

javascript
// JavaScript — 时间 O(n),空间 O(1)
function isAnagram(s, t) {
  if (s.length !== t.length) return false;
  const count = new Array(26).fill(0);
  for (let i = 0; i < s.length; i++) {
    count[s.charCodeAt(i) - 97]++;
    count[t.charCodeAt(i) - 97]--;
  }
  return count.every(c => c === 0);
}

KMP — 求 next 数组 + 匹配

javascript
// JavaScript — 时间 O(n+m)
function strStr(haystack, needle) {
  if (!needle.length) return 0;
  // 构建 next 数组(最长相等前后缀长度)
  const next = [0];
  let j = 0;
  for (let i = 1; i < needle.length; i++) {
    while (j > 0 && needle[i] !== needle[j]) j = next[j - 1];
    if (needle[i] === needle[j]) j++;
    next.push(j);
  }
  // 匹配
  j = 0;
  for (let i = 0; i < haystack.length; i++) {
    while (j > 0 && haystack[i] !== needle[j]) j = next[j - 1];
    if (haystack[i] === needle[j]) j++;
    if (j === needle.length) return i - needle.length + 1;
  }
  return -1;
}
python
# Python — KMP
def str_str(haystack: str, needle: str) -> int:
    if not needle:
        return 0
    # 构建 next
    nxt = [0] * len(needle)
    j = 0
    for i in range(1, len(needle)):
        while j > 0 and needle[i] != needle[j]:
            j = nxt[j - 1]
        if needle[i] == needle[j]:
            j += 1
        nxt[i] = j
    # 匹配
    j = 0
    for i in range(len(haystack)):
        while j > 0 and haystack[i] != needle[j]:
            j = nxt[j - 1]
        if haystack[i] == needle[j]:
            j += 1
        if j == len(needle):
            return i - len(needle) + 1
    return -1