【优选算法篇】哈希表——空间换时间的极致艺术

文章目录

    • 哈希的奥义:在混沌中建立瞬间响应
    • [一、 两数之和:一切梦开始的地方 (Easy)](#一、 两数之和:一切梦开始的地方 (Easy))
      • [1.1 题目描述](#1.1 题目描述)
      • [1.2 算法思路:边存边找](#1.2 算法思路:边存边找)
      • [1.3 C++ 代码实战](#1.3 C++ 代码实战)
    • [二、 判定是否互为字符重排 (Easy)](#二、 判定是否互为字符重排 (Easy))
      • [2.1 题目描述](#2.1 题目描述)
      • [2.2 算法思路:计数抵消法](#2.2 算法思路:计数抵消法)
      • [2.3 C++ 代码实战](#2.3 C++ 代码实战)
    • [三、 存在重复元素 I & II:查重与距离判断](#三、 存在重复元素 I & II:查重与距离判断)
      • [3.1 题目描述](#3.1 题目描述)
      • [3.2 深度拆解:贪心的下标更新](#3.2 深度拆解:贪心的下标更新)
      • [3.3 C++ 代码实战 (Q2 为例)](#3.3 C++ 代码实战 (Q2 为例))
    • [四、 字母异位词分组:哈希套容器的高阶玩法 (Medium)](#四、 字母异位词分组:哈希套容器的高阶玩法 (Medium))
      • [4.1 题目描述](#4.1 题目描述)
      • [4.2 算法思路:特征归一化](#4.2 算法思路:特征归一化)
      • [4.3 C++ 代码实战](#4.3 C++ 代码实战)
    • [五、 总结:哈希表的威力](#五、 总结:哈希表的威力)

哈希的奥义:在混沌中建立瞬间响应


一、 两数之和:一切梦开始的地方 (Easy)

1.1 题目描述

题目链接1. 两数之和

描述

给定一个整数数组 nums 和一个目标值 target,找出和为目标值的那两个整数,并返回它们的数组下标。

1.2 算法思路:边存边找

  • 传统做法 :先全部存进哈希表,再遍历一遍找 target - x。这需要处理同一个元素被用两次的问题。

  • 贪心优化 :遍历数组,每到一个数 nums[i]

    1. 计算"另一半":x = target - nums[i]
    2. 去哈希表查 x 是否在之前出现过。
    3. 如果在 :找到了!返回 {hash[x], i}
    4. 如果不在 :把当前的 nums[i] 存进哈希表,继续往后走。

ASCII 逻辑图

bash 复制代码
nums = [2, 7, 11, 15], target = 9
i=0: nums[0]=2, target-2=7. Map空. 把 {2:0} 存入.
i=1: nums[1]=7, target-7=2. Map中有2! 返回 {0, 1}.

1.3 C++ 代码实战

cpp 复制代码
class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        // key: 数值, value: 下标
        unordered_map<int, int> hash;

        for (int i = 0; i < nums.size(); i++) {
            int complement = target - nums[i];
            
            // 检查"另一半"是否已经在之前的记录里
            if (hash.count(complement)) {
                return {hash[complement], i};
            }
            
            // 没找到就存入当前值,给后面的数备查
            hash[nums[i]] = i;
        }
        return {}; // 基本不会走到这
    }
};

二、 判定是否互为字符重排 (Easy)

2.1 题目描述

题目链接面试题 01.02. 判定是否互为字符重排

描述

给定两个字符串 s1s2,请编写一个程序,确定其中一个字符串的字符重新排列后,能否变成另一个字符串。

2.2 算法思路:计数抵消法

所谓"重排",就是字母种类和个数完全一样。

  1. 长度检查 :长度不相等,直接返回 false

  2. 哈希计数:用数组模拟哈希表(因为只有字母,范围小,效率极高)。

    • 遍历 s1:每个字母对应的位置 +1
    • 遍历 s2:每个字母对应的位置 -1
    • 关键点 :如果在减的过程中,某个字母的计数值小于 0,说明 s2 中该字母多出来了,直接 false

2.3 C++ 代码实战

cpp 复制代码
class Solution {
public:
    bool CheckPermutation(string s1, string s2) {
        if (s1.size() != s2.size()) return false;

        // 只有26个字母或128个ASCII码,直接开数组
        int hash[128] = {0};

        // 1. 统计第一个字符串
        for (char ch : s1) hash[ch]++;

        // 2. 抵消第二个字符串
        for (char ch : s2) {
            hash[ch]--;
            // 如果减到负数,说明 s2 出现了 s1 没有或多出的字符
            if (hash[ch] < 0) return false;
        }

        return true;
    }
};

三、 存在重复元素 I & II:查重与距离判断

3.1 题目描述

Q1217. 存在重复元素 ------ 只要有重复就返回 true

Q2219. 存在重复元素 II ------ 重复元素的下标差 ≤ k \le k ≤k 返回 true

3.2 深度拆解:贪心的下标更新

  • Q1 简单查重 :直接用 unordered_set 记录见过的数。
  • Q2 距离查重
    我们需要记录每个数最后一次出现的位置
    为什么只需记录"最后一次"?
    假设我们找 nums[i] == nums[j]。如果 i 比较靠后,那么它和 j 越靠近,下标差就越容易 ≤ k \le k ≤k。所以,如果我们遇到一个新的重复元素,即使它不满足 ≤ k \le k ≤k,我们也要更新它的下标,因为它更有潜力满足后续的判断。

3.3 C++ 代码实战 (Q2 为例)

cpp 复制代码
class Solution {
public:
    bool containsNearbyDuplicate(vector<int>& nums, int k) {
        unordered_map<int, int> hash; // val -> index

        for (int i = 0; i < nums.size(); i++) {
            // 如果查到了重复元素
            if (hash.count(nums[i])) {
                // 且距离满足条件
                if (i - hash[nums[i]] <= k) return true;
            }
            
            // 无论满不满足,都更新为最新的下标(贪心策略)
            hash[nums[i]] = i;
        }
        return false;
    }
};

四、 字母异位词分组:哈希套容器的高阶玩法 (Medium)

4.1 题目描述

题目链接49. 字母异位词分组

描述

给你一个字符串数组,请你将字母异位词(字符相同但顺序不同)组合在一起。

4.2 算法思路:特征归一化

如何让哈希表识别出 "eat""tea" 是一家人?

  • 找共性 :把它们各自按字母序排序(sort),结果都是 "aet"

  • 哈希映射

    • Key :排序后的字符串("aet")。
    • Value :一个列表(vector<string>),存放所有排序后等于 "aet" 的原单词。

这是哈希表最强大的地方:它的 Value 可以是任何容器

4.3 C++ 代码实战

cpp 复制代码
class Solution {
public:
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        // key: 排序后的特征字符串, value: 原字符串集合
        unordered_map<string, vector<string>> hash;

        for (string& s : strs) {
            string key = s;
            sort(key.begin(), key.end()); // 排序,让全家人长得一样
            hash[key].push_back(s);       // 自动归类
        }

        // 提取结果
        vector<vector<string>> ret;
        for (auto& [key, group] : hash) {
            ret.push_back(group);
        }
        return ret;
    }
};

五、 总结:哈希表的威力

💬 复盘:哈希表不仅能存数,更能存"规律"。

  1. 数组 vs Map :字符集小用数组,离散数据大用 unordered_map
  2. 单次遍历:两数之和、重复元素 II 告诉我们,不需要先存后找,边存边找更高效。
  3. 特征化(Normalization):异位词分组告诉我们,通过排序或计数提取"特征",可以将复杂对象映射到同一个桶里。

这些题目虽然简单,但它们是构建大型系统(如数据库索引、缓存系统)的逻辑基石。

相关推荐
bbbb3652 小时前
算法调优的多目标优化与性能平衡模型的技术8
算法
Fcy6482 小时前
与二叉树有关算法题
算法·深度优先
️是782 小时前
信息奥赛一本通—编程启蒙(3346:【例60.3】 找素数)
数据结构·c++·算法
captain3762 小时前
map和set
数据结构·算法
一杯美式 no sugar2 小时前
类和对象(中)
开发语言·c++
qq_416018722 小时前
实时数据可视化库
开发语言·c++·算法
格林威2 小时前
工业相机参数解析:曝光时间与运动模糊的“生死博弈”
c++·人工智能·数码相机·opencv·算法·计算机视觉·工业相机
2401_873204652 小时前
C++中的策略模式进阶
开发语言·c++·算法
OKkankan2 小时前
深入理解linux进程
java·linux·c++