Leetcode哈希表篇

242. 有效的字母异位词

解题思路

要判断两个字符串 st 是否是字母异位词(Anagram),核心定义是:

两个字符串包含的字符种类和每种字符的出现次数完全相同,只是顺序可能不同。

例如:

  • "anagram""nagaram" → 是异位词 ✅
  • "rat""car" → 不是 ❌
🧠 关键观察:
  • 如果两个字符串长度不同,一定不是异位词(可提前返回 false)。
  • 只需关注 26 个小写字母(题目通常限定为小写英文字母)。
  • 可以用一个计数数组 来统计每个字母的"净出现次数":
    • 遍历 s:每个字符对应位置 +1
    • 遍历 t:每个字符对应位置 -1
  • 最终如果所有计数都为 0,说明两字符串字符频次完全一致 → 是异位词。

这种方法称为 "频次抵消法",时间高效,空间固定。

复制代码
class Solution {
public:
    bool isAnagram(string s, string t) {
        // 1. 定义长度为26的整型数组,初始化为0
        //    record[i] 表示第 i 个字母('a'+i)的净频次
        int record[26] = {0};

        // 2. 遍历字符串 s,对每个字符进行 +1 计数
        for (int i = 0; i < s.size(); i++) {
            record[s[i] - 'a']++;  // 将字符映射到 0~25 的索引
        }

        // 3. 遍历字符串 t,对每个字符进行 -1 抵消
        for (int j = 0; j < t.size(); j++) {
            record[t[j] - 'a']--;
        }

        // 4. 检查 record 数组是否全为 0
        for (int i = 0; i < 26; i++) {
            if (record[i] != 0) {
                return false;  // 只要有一个不为0,就不是异位词
            }
        }

        return true;  // 全为0,是异位词
    }
};

349. 两个数组的交集

解题思路

核心思想:利用哈希集合(HashSet)实现快速查找和自动去重

步骤分解:

  1. nums1 转为哈希集合 nums_set
    • 目的:去重 + O(1) 平均查找
  2. 遍历 nums2 中的每个元素 num
    • 如果 num 存在于 nums_set 中 → 说明是公共元素
    • 将其加入结果集合 result_set(自动去重)
  3. result_set 转为 vector 返回

💡 为什么用两个 unordered_set

  • 第一个 (nums_set):快速判断是否在 nums1
  • 第二个 (result_set):确保结果中无重复(即使 nums2 有多个相同交集元素)
复制代码
class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        // 1. 用 nums1 构建哈希集合(自动去重)
        unordered_set<int> nums_set(nums1.begin(), nums1.end());
        
        // 2. 用另一个集合存储交集结果(自动去重)
        unordered_set<int> result_set;
        
        // 3. 遍历 nums2
        for (int num : nums2) {
            // 如果 num 在 nums1 中出现过
            if (nums_set.find(num) != nums_set.end()) {
                result_set.insert(num); // 插入结果集(重复插入无效)
            }
        }
        
        // 4. 将 unordered_set 转为 vector 返回
        return vector<int>(result_set.begin(), result_set.end());
    }
};

202. 快乐数

解题核心思路

关键观察:

非快乐数一定会进入循环(因为平方和有上界,状态有限)。

因此,问题转化为:

在计算平方和的过程中,是否出现重复值?

解决方案:

  • 使用 unordered_set<int> 记录所有出现过的平方和。

  • 如果某次计算结果为 1 → 返回 true

  • 如果某次结果已在集合中 → 出现循环 → 返回 false

    class Solution {
    public:
    int getsum(int n){
    int sum=0;
    while(n){
    sum += (n%10)*(n%10);
    n=n/10;
    }

    复制代码
          return sum;
      }
    
      bool isHappy(int n) {
         unordered_set<int> res;
         
         while(1){
          int sum = getsum(n);
          if(sum==1)return true;
          if(res.find(sum)!= res.end()) return false;
          else res.insert(sum);
          n = sum;
         }
      }

    };

1. 两数之和

解题核心思想:"用空间换时间"

暴力解法(O(n²)):

  • 双重循环枚举所有 (i, j) 对 → 效率低 ❌

哈希表优化(O(n))✅:

  • 遍历数组时,记录每个数字及其下标
  • 对当前 nums[i],计算 need = target - nums[i]
  • 如果 need 已在哈希表中 → 找到答案!
  • 否则,将 nums[i] 存入哈希表,继续

💡 关键洞察:

当我们处理到 nums[i] 时,只需要知道 前面是否出现过 target - nums[i]

复制代码
vector<int> twoSum(vector<int>& nums, int target) {
    int n = nums.size();
    unordered_map<int, int> res; // key: 数值, value: 下标

    for (int i = 0; i < n; i++) {
        auto iter = res.find(target - nums[i]); // 查找 need = target - nums[i]
        if (iter != res.end()) {
            return {iter->second, i}; // 找到!返回 {之前下标, 当前下标}
        } else {
            res.insert(pair<int, int>(nums[i], i)); // 将当前数存入 map
        }
    }
    return {}; // 理论上不会执行(题目保证有解)
}

454. 四数相加 II

解题核心思想:分组 + 哈希查找

❌ 暴力解法(不可行):

  • 四重循环 → 时间复杂度 O(n⁴) ,当 n=200 时,操作次数 ≈ 1.6×10⁹ → 超时 ❌

✅ 优化思路(你的方法):

将四数之和拆成两部分:

复制代码
(A[i] + B[j]) + (C[k] + D[l]) == 0
⇒ A[i] + B[j] == - (C[k] + D[l])
步骤:
  1. 预处理 A+B 的所有可能和,并用哈希表记录每个和出现的次数。
  2. 遍历 C+D 的所有可能和 ,对每个 s = c + d,检查 -s 是否在哈希表中。
    • 如果存在,则说明有 umap[-s](a,b) 组合能与当前 (c,d) 配对 → 累加到结果。

💡 这是典型的 "空间换时间" + "meet-in-the-middle" 思想。

复制代码
int fourSumCount(vector<int>& A, vector<int>& B, vector<int>& C, vector<int>& D) {
    // 1. 哈希表:key = a+b 的值,value = 该值出现的次数
    unordered_map<int, int> umap;

    // 2. 枚举 A 和 B 的所有组合,统计 a+b
    for (int a : A) {
        for (int b : B) {
            umap[a + b]++;  // 自动初始化为 0 后 +1
        }
    }

    int count = 0;

    // 3. 枚举 C 和 D 的所有组合
    for (int c : C) {
        for (int d : D) {
            int target = -(c + d);
            if (umap.find(target) != umap.end()) {
                count += umap[target]; // 累加所有匹配的 (a,b) 数量
            }
        }
    }

    return count;
}

15. 三数之和

复制代码
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> res;
        sort(nums.begin(), nums.end());
        int n = nums.size();

        for (int i = 0; i < n; i++) {
            if (i > 0 && nums[i] == nums[i - 1]) 
                continue;

            int left = i + 1, right = n - 1;
            while (left < right) {
                int sum = nums[i] + nums[left] + nums[right];
                if (sum == 0) {
                    res.push_back({nums[i], nums[left], nums[right]});
                    
                    left++;
                    right--;
                    
                    // 跳过重复值
                    while (left < right && nums[left] == nums[left - 1])
                        left++;
                    while (left < right && nums[right] == nums[right + 1])
                        right--;
                }
                else if (sum < 0) {
                    left++;
                }
                else {
                    right--;
                }
            }
        }
        return res;
    }
};

18. 四数之和

暴力解法 vs 优化思路

❌ 暴力解法(O(n⁴)):

  • 四重循环枚举所有 (i, j, k, l) 组合
  • 时间复杂度过高(n=200 时,操作数 ≈ 1.6 亿),会超时

✅ 优化方向:排序 + 双指针 + 剪枝 + 去重

这是解决 KSum 类问题(2Sum, 3Sum, 4Sum...) 的通用范式。

核心解题步骤(以 4Sum 为例)

步骤 1️⃣:排序数组

复制代码
sort(nums.begin(), nums.end());
  • 作用:
    • 使双指针可行(有序才能根据和的大小移动指针)
    • 便于去重(相同元素相邻)

步骤 2️⃣:固定前两个数,双指针找后两个数

  • 外层循环:固定第一个数 nums[k]
  • 内层循环:固定第二个数 nums[i]i > k
  • 然后在 [i+1, n-1] 区间用 双指针cd

相当于把 4Sum 拆成

复制代码
(a + b) + (c + d) = target

步骤 3️⃣:关键优化 ------ 剪枝(Pruning)

避免无效搜索,大幅提升效率。

✂️ 剪枝 1:当前最小四元组 > target → 后续无解
复制代码
if ((long)nums[k] + nums[k+1] + nums[k+2] + nums[k+3] > target) break;
  • 因为数组已排序,k 固定时,最小和是接下来三个数
  • 如果这个最小和都 > target,说明 k 太大,直接跳出外层循环
✂️ 剪枝 2:当前最大四元组 < target → 当前 k/i 太小
复制代码
if ((long)nums[k] + nums[n-3] + nums[n-2] + nums[n-1] < target) continue;
  • 最大和是最后三个数
  • 如果最大和都 < target,说明当前 k(或 i)太小,跳过本次循环

💡 注意:必须加 (long) 防止 int 溢出!


步骤 4️⃣:去重(Avoid Duplicates)

因为数组可能有重复元素,必须跳过相同值,避免重复四元组。

去重规则:
  1. 第一个数去重

    复制代码
    if (k > 0 && nums[k] == nums[k-1]) continue;
  2. 第二个数去重

    复制代码
    if (i > k+1 && nums[i] == nums[i-1]) continue;
  3. 第三、四个数去重(找到解后)

    复制代码
    left++; right--;
    while (left < right && nums[left] == nums[left-1]) left++;
    while (left < right && nums[right] == nums[right+1]) right++;

✅ 原则:每个位置只取"第一次出现"的值


步骤 5️⃣:双指针移动逻辑

复制代码
long sum = (long)nums[k] + nums[i] + nums[left] + nums[right];
if (sum > target) {
    right--;      // 和太大,右指针左移
} else if (sum < target) {
    left++;       // 和太小,左指针右移
} else {
    // 找到解,加入结果,并去重移动
}

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        vector<vector<int>> result;
        sort(nums.begin(), nums.end());
        int n = nums.size();
        
        for (int k = 0; k < n; k++) {
            // 一级剪枝:最小四元组 > target
            if (k + 3 < n && (long)nums[k] + nums[k+1] + nums[k+2] + nums[k+3] > target) {
                break;
            }
            // 最大四元组 < target,跳过(可选优化)
            if ((long)nums[k] + nums[n-3] + nums[n-2] + nums[n-1] < target) {
                continue;
            }
            if (k > 0 && nums[k] == nums[k-1]) continue;

            for (int i = k + 1; i < n; i++) {
                // 二级剪枝:最小四元组 > target
                if (i + 2 < n && (long)nums[k] + nums[i] + nums[i+1] + nums[i+2] > target) {
                    break;
                }
                // 最大四元组 < target,跳过
                if ((long)nums[k] + nums[i] + nums[n-2] + nums[n-1] < target) {
                    continue;
                }
                if (i > k + 1 && nums[i] == nums[i-1]) continue;

                int left = i + 1, right = n - 1;
                while (left < right) {
                    long sum = (long)nums[k] + nums[i] + nums[left] + nums[right];
                    if (sum > target) {
                        right--;
                    } else if (sum < target) {
                        left++;
                    } else {
                        result.push_back({nums[k], nums[i], nums[left], nums[right]});
                        left++;
                        right--;
                        // 去重
                        while (left < right && nums[left] == nums[left - 1]) left++;
                        while (left < right && nums[right] == nums[right + 1]) right++;
                    }
                }
            }
        }
        return result;
    }
};
相关推荐
独自破碎E2 小时前
【字节面试手撕】大数加法
java·算法
鱼跃鹰飞2 小时前
LeetCode热题100: 49.字母异位词分组
java·数据结构·算法·leetcode
myloveasuka2 小时前
3-8 译码器(正式型号74LS138、 74HC138、74HCT138 等))
笔记·算法·计算机组成原理·硬件
wen__xvn2 小时前
基础算法集训第17天:二分查找
算法·leetcode·职场和发展
myloveasuka2 小时前
MREQ̅ 信号
笔记·算法·计算机组成原理
亲爱的非洲野猪2 小时前
动态规划进阶:区间DP深度解析
算法·动态规划
QiZhang | UESTC2 小时前
【算法题学习方法调整】回溯核心逻辑调整:从记代码到套逻辑调整
算法·学习方法
救救孩子把2 小时前
59-机器学习与大模型开发数学教程-5-6 Adam、RMSProp、AdaGrad 等自适应优化算法
人工智能·算法·机器学习
Σίσυφος19002 小时前
PCL 中常用的滤波对比
算法