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

文章目录

    • 哈希的奥义:在混沌中建立瞬间响应
    • [一、 两数之和:一切梦开始的地方 (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):异位词分组告诉我们,通过排序或计数提取"特征",可以将复杂对象映射到同一个桶里。

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

相关推荐
bIo7lyA8v1 小时前
算法稳定性分析中的输入扰动建模的技术9
算法
CoderCodingNo1 小时前
【GESP】C++三级真题 luogu-B4499, [GESP202603 三级] 二进制回文串
数据结构·c++·算法
sinat_286945191 小时前
AI Coding 时代的 TDD:从理念到工程落地
人工智能·深度学习·算法·tdd
炽烈小老头1 小时前
【 每天学习一点算法 2026/04/12】x 的平方根
学习·算法
ASKED_20191 小时前
从排序到生成:腾讯广告算法大赛 2025 baseline解读
人工智能·算法
田梓燊1 小时前
leetcode 160
算法·leetcode·职场和发展
_深海凉_1 小时前
LeetCode热题100-颜色分类
python·算法·leetcode
hetao17338372 小时前
2026-04-09~12 hetao1733837 的刷题记录
c++·算法
6Hzlia2 小时前
【Hot 100 刷题计划】 LeetCode 136. 只出现一次的数字 | C++ 哈希表&异或基础解法
c++·算法·leetcode
MWWZ3 小时前
最近的一些软件更新
opencv·算法·计算机视觉