代码随想录:哈希表篇

242. 有效的字母异位词

题目描述

给定两个字符串 st,判断 t 是否是 s 的字母异位词(字符种类和数量完全相同,顺序不同)。假设字符串仅包含小写字母。

  • 示例 1:输入 s = "anagram", t = "nagaram",输出 true
  • 示例 2:输入 s = "rat", t = "car",输出 false

核心思路

利用数组模拟哈希表(小写字母仅26个,数组大小固定为26),统计字符出现次数:

  1. 遍历 s,统计每个字符出现次数(数组对应索引+1);
  2. 遍历 t,抵消对应字符的统计次数(数组对应索引-1);
  3. 校验数组是否全为0,全0则为异位词,否则不是。

C++代码(增加长度预判,提升效率)

cpp 复制代码
class Solution {
public:
    bool isAnagram(string s, string t) {
        if (s.size() != t.size()) return false; // 长度不同直接排除
        int record[26] = {0};
        for (int i = 0; i < s.size(); i++) record[s[i] - 'a']++;
        for (int i = 0; i < t.size(); i++) record[t[i] - 'a']--;
        for (int i = 0; i < 26; i++) if (record[i]) return false;
        return true;
    }
};

关键说明

  1. 字符映射s[i] - 'a' 将小写字母映射到0-25的数组索引(无需记忆ASCII值,仅需相对位置);
  2. 复杂度:时间 O(n)(仅三次线性遍历),空间 O(1)(固定26长度数组,与输入无关);
  3. 精简点:去掉冗余注释、合并换行,保留核心逻辑,语法极简且不影响可读性。

总结

  1. 核心是利用数组实现哈希统计,适配小写字母的有限范围;
  2. 长度预判是工程常用优化,可避免无效的字符统计;
  3. 代码精简的核心是保留「统计-抵消-校验」的核心逻辑,剔除冗余语法。

349. 两个数组的交集

题目描述

给定两个数组,计算它们的交集,输出结果中每个元素唯一,且不考虑顺序。

核心思路

本题核心是利用哈希表去重+快速查找 ,因题目补充了数值范围(0≤数值≤1000),优先用数组实现哈希(效率更高);无范围限制时用unordered_set(更通用)。

C++代码

方案1:数组哈希(适配数值范围限制,最优)

cpp 复制代码
class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        unordered_set<int> res;
        int hash[1005] = {0};
        for (int num : nums1) hash[num] = 1;
        for (int num : nums2) if (hash[num]) res.insert(num);
        return vector<int>(res.begin(), res.end());
    }
};

方案2:unordered_set(通用版,无数值范围限制)

cpp 复制代码
class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        unordered_set<int> res, nums_set(nums1.begin(), nums1.end());
        for (int num : nums2) if (nums_set.count(num)) res.insert(num);
        return vector<int>(res.begin(), res.end());
    }
};

关键说明

  1. 数组哈希:利用0-1000的数值范围,用长度1005的数组标记nums1元素,查找时间O(1),空间更省;
  2. unordered_set:底层哈希表,读写效率高,无需排序,适配任意数值范围;
  3. 去重逻辑 :结果用unordered_set存储,天然去重,最后转vector返回。

复杂度

  • 时间:O(m+n)(m、n为两数组长度,仅线性遍历+哈希查找);
  • 空间:O(n)(哈希存储nums1元素或结果集)。

总结

  1. 有数值范围限制时,优先用数组哈希(速度快、空间省);
  2. 无范围限制时,用unordered_set(通用灵活);
  3. 核心逻辑:哈希标记存在性 → 遍历另一数组筛选交集 → 去重后返回。

1. 两数之和

题目描述

给定整数数组 nums 和目标值 target,找出数组中两个数之和等于 target 的下标,保证有且仅有一个答案,且同一元素不能重复使用。

  • 示例:输入 nums = [2,7,11,15], target = 9,输出 [0,1]

核心思路

暴力解法时间复杂度 O(n²),最优解用哈希表(unordered_map) 实现 O(n) 复杂度:

  1. 哈希表存储「已遍历元素值 → 下标」,避免重复遍历;
  2. 遍历数组时,计算当前元素的「补数」(target - nums[i]);
  3. 若补数存在于哈希表中,直接返回补数下标和当前下标;若不存在,将当前元素和下标存入哈希表。

C++代码

cpp 复制代码
class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int, int> mp;
        for (int i = 0; i < nums.size(); i++) {
            auto iter = mp.find(target - nums[i]);
            if (iter != mp.end()) return {iter->second, i};
            mp[nums[i]] = i; // 简化insert写法,等价于mp.insert({nums[i], i})
        }
        return {};
    }
};

关键说明

  1. 哈希表选择 :用 unordered_map(哈希表实现)而非 map(红黑树),查询/插入效率 O(1),无需有序性;
  2. 核心逻辑:「空间换时间」,通过哈希表记录已遍历元素,避免内层循环;
  3. 存储设计:key = 数组元素值(用于快速查找补数),value = 元素下标(用于返回结果)。

复杂度

  • 时间:O(n)(仅一次遍历,哈希表查找为O(1));
  • 空间:O(n)(最坏情况存储n-1个元素)。

总结

  1. 用哈希表的核心目的:快速判断「补数是否已出现」,而非暴力遍历;
  2. 选择 unordered_map 是因无需key有序,追求最高读写效率;
  3. 哈希表存储「已遍历元素值-下标」,遍历中实时查询补数,找到即返回。

454. 四数相加II

题目描述

给定四个等长数组 A、B、C、D,计算满足 A[i]+B[j]+C[k]+D[l]=0 的元组 (i,j,k,l) 数量。

  • 示例:输入 A=[1,2], B=[-2,-1], C=[-1,2], D=[0,2],输出 2

核心思路

暴力解法时间复杂度 O(n⁴),最优解通过哈希表拆分计算,将复杂度降至 O(n²):

  1. 拆分四数和为「两数和(A+B)」+「两数和(C+D)=0」,即 A+B = -(C+D)
  2. 先用哈希表统计 A+B 所有可能的和及出现次数;
  3. 遍历 C+D 的所有和,查找哈希表中是否存在「-(C+D)」,存在则累加对应次数。

C++代码

cpp 复制代码
class Solution {
public:
    int fourSumCount(vector<int>& A, vector<int>& B, vector<int>& C, vector<int>& D) {
        unordered_map<int, int> sum_ab;
        // 统计A+B的和及出现次数
        for (int a : A) for (int b : B) sum_ab[a + b]++;
        
        int count = 0;
        // 查找-(C+D)在sum_ab中的次数并累加
        for (int c : C) for (int d : D) count += sum_ab[-c - d];
        
        return count;
    }
};

关键说明

  1. 拆分逻辑:将四数和拆分为两组两数和,避免四层循环,时间复杂度从 O(n⁴) 降至 O(n²);
  2. 哈希表设计:key = A+B 的和,value = 该和出现的次数(统计所有可能的(i,j)组合数);
  3. 简化查找-c-d 等价于 0-(c+d),直接作为key查询哈希表,存在则累加对应次数(即满足条件的组合数)。

复杂度

  • 时间:O(n²)(两层循环统计A+B,两层循环统计C+D并查询);
  • 空间:O(n²)(最坏情况A+B的和互不重复,哈希表存储n²个键值对)。

总结

  1. 核心巧思:拆分问题+哈希表统计,用空间换时间;
  2. 与三数之和/四数之和的区别:本题是四个独立数组,无需去重,哈希法适配性极高;
  3. 关键逻辑:统计A+B的和→遍历C+D找互补和→累加符合条件的组合数。

15. 三数之和

题目描述

给定整数数组 nums,找出所有和为 0 且不重复的三元组 [a,b,c],要求三元组不能重复,且元素下标互不相同。

  • 示例:输入 nums = [-1,0,1,2,-1,-4],输出 [[-1,0,1],[-1,-1,2]]

核心思路

哈希法需处理大量去重细节,最优解为排序+双指针,时间复杂度 O(n²)、空间复杂度 O(1)(排序的系统空间不计):

  1. 先排序数组,便于去重和双指针移动;
  2. 固定第一个数 a = nums[i],用左指针 left = i+1、右指针 right = 数组末尾b + c = -a
  3. 按三数之和的大小移动指针:和>0则右指针左移,和<0则左指针右移,和=0则记录结果并去重。

C++代码(双指针最优版)

cpp 复制代码
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> res;
        sort(nums.begin(), nums.end());
        
        for (int i = 0; i < nums.size(); i++) {
            if (nums[i] > 0) break; // 正数无法凑出和为0,直接退出
            if (i > 0 && nums[i] == nums[i-1]) continue; // 先手去重a
            
            int left = i + 1, right = nums.size() - 1;
            while (right > left) {
                int sum = nums[i] + nums[left] + nums[right];
                if (sum > 0) right--;
                else if (sum < 0) left++;
                else {
                    res.push_back({nums[i], nums[left], nums[right]});
                    // 后手去重b和c
                    while (right > left && nums[right] == nums[right-1]) right--;
                    while (right > left && nums[left] == nums[left+1]) left++;
                    // 找到有效组合,双指针同时收缩
                    right--;
                    left++;
                }
            }
        }
        return res;
    }
};

关键说明

  1. 排序的作用:① 方便去重;② 可通过指针移动调整三数之和的大小;
  2. 去重细节
    • a的去重:跳过与前一个元素相同的nums[i](避免重复三元组);
    • b/c的去重:找到有效组合后,跳过连续重复的b/c;
  3. 剪枝优化:若nums[i]>0,因数组已排序,后续数更大,无法凑出和为0,直接退出循环。

复杂度

  • 时间:O(n²)(排序 O(n log n) + 双指针遍历 O(n²),整体由O(n²)主导);
  • 空间:O(1)(仅额外使用指针和结果数组,排序的系统空间不计入)。

总结

  1. 核心逻辑:排序固定a + 双指针找b/c,替代暴力三层循环和复杂的哈希去重;
  2. 去重关键:a对比前一个元素(先手去重),b/c在找到有效组合后跳过重复值(后手去重);
  3. 剪枝优化:正数直接退出,减少无效遍历。

18. 四数之和

题目描述

给定整数数组 nums 和目标值 target,找出所有和为 target 且不重复的四元组 [a,b,c,d],答案中不可包含重复四元组。

  • 示例:输入 nums = [1,0,-1,0,-2,2], target = 0,输出 [[-1,0,0,1],[-2,-1,1,2],[-2,0,0,2]]

核心思路

在三数之和的「排序+双指针」基础上嵌套一层for循环,将时间复杂度从暴力O(n⁴)降至O(n³):

  1. 排序数组,便于去重和指针移动;
  2. 两层for循环固定前两个数 a=nums[k]b=nums[i]
  3. 双指针 left=i+1right=数组末尾c+d = target - a - b
  4. 增加剪枝和去重逻辑,避免无效遍历和重复四元组。

C++代码

cpp 复制代码
class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        vector<vector<int>> res;
        sort(nums.begin(), nums.end());
        
        for (int k = 0; k < nums.size(); k++) {//靠left<right约束即可,无需-3
            // 一级剪枝:正数且超过target则无需继续
            if (nums[k] > target && nums[k] >= 0) break;
            // 去重a
            if (k > 0 && nums[k] == nums[k-1]) continue;
            // 未找到元素时去重用if且永远跟前一个比较过的比↑↑↑
            for (int i = k+1; i < nums.size(); i++) {//靠left<right约束即可,无需-2
                // 二级剪枝:前两数和超过target且非负则无需继续
                if (nums[k]+nums[i] > target && nums[i] >= 0) break;
                // 去重b
                if (i > k+1 && nums[i] == nums[i-1]) continue;
                // 未找到元素时去重用if且永远跟前一个比较过的比↑↑↑
                int left = i+1, right = nums.size()-1;
                while (right > left) {
                    // 用long避免溢出
                    long sum = (long)nums[k] + nums[i] + nums[left] + nums[right];
                    if (sum > target) right--;
                    else if (sum < target) left++;
                    else {
                        res.push_back({nums[k], nums[i], nums[left], nums[right]});
                        // 去重c、d(找到元素时去重用while且永远跟后一个比较过的比↓↓↓)
                        while (right>left && nums[right]==nums[right-1]) right--;
                        while (right>left && nums[left]==nums[left+1]) left++;
                        // 收缩指针
                        right--;
                        left++;
                    }
                }
            }
        }
        return res;
    }
};

关键说明

若第一个数为正且超过target,后续数更大,直接break;

若前两数和为正且超过target,后续数更大,直接break;

  • a去重:跳过与前一个相同的nums[k](k>0时);
  • b去重:跳过与前一个相同的nums[i](i>k+1时,避免误删k=i-1的情况);
  • c/d去重:找到有效组合后,跳过连续重复的c/d;
  1. 溢出处理 :用long类型存储四数之和,避免int溢出。

关键说明

  1. 剪枝优化
    • 一级剪枝:若第一个数为正且超过 target,因数组已排序后续数更大,无法凑出 target,直接 break;
    • 二级剪枝:若第一个数和第二个数的和为正且超过 target,因数组已排序后续数更大,无法凑出 target,直接 break;
  2. 去重逻辑
    • a 去重:枚举第一个数时,跳过与前一个相同的 nums [k](k>0 时),避免重复枚举;
    • b 去重:枚举第二个数时,跳过与前一个相同的 nums [i](i>k+1 时),避免误删 k=i-1 的合法情况;
    • c/d 去重:找到有效四数组合后,用 while 循环跳过 left/right 指针指向的连续重复元素,避免重复添加相同组合;
  3. 溢出处理 :用long类型存储四数之和,避免四个 int 类型数值相加超出 int 范围导致的计算错误;
    循环范围约束:外层 / 内层循环无需限制k < nums.size()-3、i < nums.size()-2,仅靠内部双指针right > left即可约束四数的有效范围,避免漏解;
    去重语法差异:枚举阶段(k/i)的去重使用if判断(仅跳过一次重复),找到组合后的去重使用while循环(跳过所有连续重复),逻辑更精准。

复杂度

  • 时间:O(n³)(排序O(n log n) + 三层遍历O(n³),整体由O(n³)主导);
  • 空间:O(1)(仅额外使用指针和结果数组,排序的系统空间不计入)。

总结

  1. 核心逻辑:排序 + 两层for固定前两数 + 双指针找后两数,复用三数之和的双指针思路;
  2. 关键细节:剪枝要考虑target为负数的情况,去重需避免误删有效组合,求和时防止溢出;
  3. 扩展:五数/六数之和可沿用「多一层for循环 + 双指针」的思路,时间复杂度递增一个量级。
相关推荐
智者知已应修善业2 小时前
【PAT乙级真题解惑1012数字分类】2025-3-29
c语言·c++·经验分享·笔记·算法
每天要多喝水2 小时前
动态规划Day30:买卖股票
算法·动态规划
v_for_van2 小时前
力扣刷题记录6(无算法背景,纯C语言)
c语言·算法·leetcode
-To be number.wan3 小时前
算法学习日记 | 双指针
c++·学习·算法
样例过了就是过了3 小时前
LeetCode热题100 最大子数组和
数据结构·算法·leetcode
铸人3 小时前
再论自然数全加和 - 欧拉伽马常数
数学·算法·数论·复数
m0_531237174 小时前
C语言-变量,枚举常量,字符串,打印类型,转义字符
c语言·数据结构·算法
zyeyeye4 小时前
自定义类型:结构体
c语言·开发语言·数据结构·c++·算法
俩娃妈教编程4 小时前
2023 年 03 月 二级真题(1)--画三角形
c++·算法·双层循环