[LeetCode] 哈希表完整版 — 哈希数组 | Set | Map

ops/2025/1/27 12:45:37/

哈希表

  • 基础知识
  • 常见的哈希结构
  • 数组
    • 242# 有效的字母异位词
    • 383# 赎金信
  • Set
    • 基础语句
    • 349# 两个数组的交集
    • 202# 快乐数
    • 15# 三数之和
    • 18# 四数之和
  • Map
    • 基础语句
    • 1# 两数之和
    • 454# 四数相加II

基础知识

哈希表常用于快速判断一个元素是否在集合中,空间换时间

哈希表是根据key(如数组的索引下标)直接进行访问的数据结构

哈希函数:将key映射到哈希表上的索引 index = hashFunction(key) = (hashCode(key) % tableSize) mod tableSize

hashCode()通过特定编码方式把key转化为数值

哈希碰撞:不同的key引射到了相同的下标,解决方法:拉链法线性探测法

  • 拉链法:将发生冲突的元素存储在链表中,不需要tableSize大于dataSize

  • 线性探测法:向下找空位,需要tableSize一定大于dataSize

常见的哈希结构

  • 数组

​ 常用于key的数值十分受限,密度高的情况,相比起set时间和空间复杂度低(set把数值映射到key上要做hash计算)

  • set(集合)

    常用于key较少、分散且数值跨度大的情况(使用数组会造成空间的极大浪费)

一般来说,当要使用集合来解决哈希问题的时候,优先使用unordered_set,因为它的查询和增删效率是最优的;如果需要集合是有序的则用set;如果要求不仅有序还要有重复数据则用multiset

map中对key是有限制,而对value没有限制,因为key的存储方式是用红黑树实现的

hash_sethash_mapunordered_setunordered_map的关系:

功能相同, 但unordered_setC++11被引入标准库,因此建议使用unordered_set

数组

常用于key的数值十分受限,密度高的情况,相比起set时间和空间复杂度低(set把数值映射到key上要做hash计算)

242# 有效的字母异位词

给定两个字符串 st ,编写一个函数来判断 t 是否是 s 的 字母异位词。

字母异位词是通过重新排列不同单词或短语的字母而形成的单词或短语,并使用所有原字母一次。

示例 1:

输入: s = "anagram", t = "nagaram"
输出: true

示例 2:

输入: s = "rat", t = "car"
输出: false

提示:

  • 1 <= s.length, t.length <= 5 * 104
  • st 仅包含小写字母

由于字符串只有小写字母,因此定义一个长度为26的数组record记录字符串中字符出现的次数

// 哈希数组
// O(n) 0ms; O(1) 9.51MB
class Solution {
public:bool isAnagram(string s, string t) {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] != 0) return false;}return true;}
};

空间复杂度:一个常量大小的辅助数组O(26),即 O(1) 或 O(S)

383# 赎金信

给你两个字符串:ransomNotemagazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。

如果可以,返回 true ;否则返回 false

magazine 中的每个字符只能在 ransomNote 中使用一次。

示例 1:

输入:ransomNote = "a", magazine = "b"
输出:false

示例 2:

输入:ransomNote = "aa", magazine = "ab"
输出:false

示例 3:

输入:ransomNote = "aa", magazine = "aab"
输出:true

提示:

  • 1 <= ransomNote.length, magazine.length <= 105
  • ransomNotemagazine 由小写英文字母组成
// 哈希数组
// O(n) 0ms; O(1) 11.46MB
class Solution {
public:bool canConstruct(string ransomNote, string magazine) {int record[26] = {0};if (ransomNote.size() > magazine.size()) return false;for (int i = 0; i < magazine.size(); i++) record[magazine[i] - 'a']++;for (int i = 0; i < ransomNote.size(); i++) record[ransomNote[i] - 'a']--;for (int i = 0; i < 26; i++) {if (record[i] < 0) return false;}return true;}
};

Set

常用于key较少、分散且数值跨度大的情况(使用数组会造成空间的极大浪费)

基础语句

// 创建
unordered_set<int> hashset;
unordered_set<int> hashset(vector.begin(), vector.end()); // 范围构造函数,参数为两个迭代器,将vector的所有元素插入hashset中,遍历O(n),插入所有元素到set的时间复杂度为O(n)~O(n^2)(哈希冲突严重)
// 查询
unordered_set.find(key)  // O(1) 如果key存在,返回一个指向该元素的迭代器;不存在返回特殊值unordered_set.end()
// 插入
unordered_set.insert(key)
// 删除
unordered_set.erase(key)
// 长度
unordered_set.size()
// 桶数量
unordered_set.bucket_count() // unordered_set会动态扩容(rehashing)

349# 两个数组的交集

给定两个数组 nums1nums2 ,返回它们的 交集。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序

数组的交集:同时出现在数组中的元素的集合

示例 1:

输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2]

示例 2:

输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[9,4]
解释:[4,9] 也是可通过的

提示:

  • 1 <= nums1.length, nums2.length <= 1000
  • 0 <= nums1[i], nums2[i] <= 1000

唯一且无序,选择unordered_set

先将nums1转化为unordered_set,再取unordered_setnums2的交集

// unordered_set
// O(n+m) 8ms; O(n) 14.43MB
class Solution {
public:vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {unordered_set<int> nums1_set(nums1.begin(), nums1.end());unordered_set<int> result_set; // 使用set为最终结果去重for (int num : nums2) {if (nums1_set.find(num) != nums1_set.end()) result_set.insert(num);}return vector<int>(result_set.begin(), result_set.end());}
};

由于本题力扣对数组的数值进行了大幅限制,因此也可使用哈希数组

// 哈希数组 & unordered_set
// O(n+m) 4ms; O(n) 14.58MB
class Solution {
public:vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {unordered_set<int> result_set;int hash[1001] = {0};for (int num : nums1) {hash[num] = 1;}for (int num : nums2) {if (hash[num] == 1) result_set.insert(num);}return vector<int>(result_set.begin(), result_set.end());}
};

202# 快乐数

编写一个算法来判断一个数 n 是不是快乐数。

「快乐数」 定义为:

  • 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
  • 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
  • 如果这个过程 结果为 1,那么这个数就是快乐数。

如果 n快乐数 就返回 true ;不是,则返回 false

示例 1:

输入:n = 19
输出:true
解释:
1^2 + 9^2 = 82
8^2 + 2^2 = 68
6^2 + 8^2 = 100
1^2 + 0^2 + 0^2 = 1

示例 2:

输入:n = 2
输出:false

提示:

  • 1 <= n <= 2^31 - 1

该题的突破点在于无限循环(结果会收敛到1 或 无限循环)

无限循环的原因:对于一个非负整数,其每位平方和范围有限(如三位数的每位平方和最大为243)

// unordered_set
// O(logn) 0ms; O(logn) 8.25MB
class Solution {
public:int getSum(int n) {int sum = 0;while (n) {sum += (n % 10) * (n % 10);n /= 10;}return sum;}bool isHappy(int n) {unordered_set<int> hashset;while (n != 1) {if (hashset.find(n) != hashset.end()) return false;hashset.insert(n);n = getSum(n); }return true;}
};

用集合记录可能导致集合过大,递归的层次较深也会导致调用栈崩溃

另一种方法是快慢指针法

每位平方和的过程是一个隐式链表,则转换为链表中是否有环的问题,fast走两步,slow走一步(相对速度为1),二者相等即存在环

由于该题的特殊性——为1时1自身形成环,因此只需判断fastslow相遇时(环中)是否为1

// 快慢指针法
// O(logn) 0ms; O(1) 7.6MB
class Solution {
public:int bitSquareSum(int n) {int sum = 0;while (n) {sum += (n % 10) * (n % 10);n /= 10;}return sum;}bool isHappy(int n) {int slow = n, fast = n;do {slow = bitSquareSum(slow);fast = bitSquareSum(bitSquareSum(fast));} while (slow != fast);return slow == 1;}
};

15# 三数之和

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != kj != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。

**注意:**答案中不可以包含重复的三元组。

示例 1:

输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1][-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。

示例 2:

输入:nums = [0,1,1]
输出:[]
解释:唯一可能的三元组和不为 0

示例 3:

输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0

提示:

  • 3 <= nums.length <= 3000
  • -10^5 <= nums[i] <= 10^5

三元组不可重复(三元组内的元素可以重复),而最后再对三元组去重非常耗时,在寻找三元组的过程中去重(部分借助set

// unordered_set
// O(n^2) 1897ms; O(n) 437.43MB
class Solution {
public:vector<vector<int>> threeSum(vector<int>& nums) {vector<vector<int>> result;int length = nums.size();sort(nums.begin(), nums.end()); // 排序简化问题for (int i = 0; i < length - 2; i++) {if (nums[i] > 0) break;if(i > 0 && nums[i] == nums[i - 1]) continue; // 去重aunordered_set<int> hashset; // 存储去重的b,由于b使用后擦除,因此在b!=c时做到去重b和cfor (int k = i + 1; k < length; k++) {if (k > i + 2 && nums[k] == nums[k - 1] && nums[k - 1] == nums[k - 2])continue; // 去重b=c时的b和cint target = 0 - (nums[i] + nums[k]);if (hashset.find(target) != hashset.end()) {result.push_back({nums[i], target, nums[k]}); // nums[k]为chashset.erase(target);} else {hashset.insert(nums[k]); // nums[k]为b}}}return result;}
};

双指针法:将寻找bc的过程转换为双指针查找

两数之和需要返回索引下标(三数之和不需要),而双指针法一定要排序,因此在两数之和中不使用双指针法

// 双指针法
// O(n^2) 55ms; O(1) 28.47MB
class Solution {
public:vector<vector<int>> threeSum(vector<int>& nums) {vector<vector<int>> result;sort(nums.begin(), nums.end());int n = nums.size();for (int i = 0; i < n - 2; i++) {if (nums[i] > 0) break;if(i > 0 && nums[i] == nums[i - 1]) continue; // 去重aint left = i + 1, right = n - 1;while (left < right) {if (nums[i] + nums[left] + nums[right] > 0) {right--; // 这里无论去不去重right依旧--,因此不需要去重} else if (nums[i] + nums[left] + nums[right] < 0) {left++;} else {result.push_back({nums[i], nums[left], nums[right]});left++;right--;// 去重b和cwhile (left < right && nums[left] == nums[left - 1]) left++;while (left < right && nums[right] == nums[right + 1]) right--;} }}return result;}
};

18# 四数之和

给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):

  • 0 <= a, b, c, d < n
  • abcd 互不相同
  • nums[a] + nums[b] + nums[c] + nums[d] == target

你可以按 任意顺序 返回答案 。

示例 1:

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

示例 2:

输入:nums = [2,2,2,2,2], target = 8
输出:[[2,2,2,2]]

提示:

  • 1 <= nums.length <= 200
  • -10^9 <= nums[i] <= 10^9
  • -10^9 <= target <= 10^9
// 双指针法
// O(n^3) 27ms; O(1) 17.18MB
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 i = 0; i < n - 3; i++) {if (nums[i] > target && nums[i] >= 0) break; // nums[i]>=0保证四数之和不会减小if (i > 0 && nums[i] == nums[i - 1]) continue; // 去重afor (int j = i + 1; j < n - 2; j++) {if (nums[i] + nums[j] > target && nums[i] + nums[j] >= 0) break;if (j > i + 1 && nums[j] == nums[j - 1]) continue; // 去重bint left = j + 1, right = n - 1;while(left < right) {// nums[i] + nums[j] + nums[left] + nums[right] 会溢出if ((long) nums[i] + nums[j] + nums[left] + nums[right] < target) {left++;} else if ((long) nums[i] + nums[j] + nums[left] + nums[right] > target) {right--;} else {result.push_back({nums[i], nums[j], nums[left], nums[right]});left++;right--;// 去重c和dwhile (left < right && nums[left] == nums[left - 1]) left++;while (left < right && nums[right] == nums[right + 1]) right--;}}}}return result;}
};

Map

基础语句

// 创建
unordered_map<int, int> hashmap;// 查询
auto iter = unordered_map.find(key); // O(1),不存在返回 unordered_map.end()
iter->first // 查key
iter->second // 查value
auto iter = M.begin(); // 第一个元素
iter+1 // 下一个元素// 插入
unordered_map[key] = value;
unordered_map.insert(pair<int, int>(key, value));

1# 两数之和

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

你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。

你可以按任意顺序返回答案。

示例 1:

输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1]

示例 2:

输入:nums = [3,2,4], target = 6
输出:[1,2]

示例 3:

输入:nums = [3,3], target = 6
输出:[0,1]

提示:

  • 2 <= nums.length <= 104
  • -10^9 <= nums[i] <= 10^9
  • -10^9 <= target <= 10^9
  • 只会存在一个有效答案

需要查询元素的同时,还需要元素对应的下标,因此用mapkey可以无需,因此用unordered_map

{key:数据元素,value:对应下标}

// unordered_map
// O(n) 3ms; O(n) 14.59MB
class Solution {
public:vector<int> twoSum(vector<int>& nums, int target) {unordered_map<int, int> hashmap;for (int i = 0; i < nums.size(); i++) {auto iter = hashmap.find(target - nums[i]);if (iter != hashmap.end()) return {iter->second, i};hashmap[nums[i]] = i;// hashmap.insert(pair<int, int>(nums[i], i));}return {};}
};

454# 四数相加II

给你四个整数数组 nums1nums2nums3nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足:

  • 0 <= i, j, k, l < n
  • nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0

示例 1:

输入:nums1 = [1,2], nums2 = [-2,-1], nums3 = [-1,2], nums4 = [0,2]
输出:2
解释:
两个元组如下:
1. (0, 0, 0, 1) -> nums1[0] + nums2[0] + nums3[0] + nums4[1] = 1 + (-2) + (-1) + 2 = 0
2. (1, 1, 0, 0) -> nums1[1] + nums2[1] + nums3[0] + nums4[0] = 2 + (-1) + (-1) + 0 = 0

示例 2:

输入:nums1 = [0], nums2 = [0], nums3 = [0], nums4 = [0]
输出:1

提示:

  • n == nums1.length
  • n == nums2.length
  • n == nums3.length
  • n == nums4.length
  • 1 <= n <= 200
  • -228 <= nums1[i], nums2[i], nums3[i], nums4[i] <= 228

对问题拆分为两个两数相加,使用unordered_mapkey记录两数之和,value记录两数之和出现的次数(求次数)

# unordered_map
# O(n^2) 130ms; O(n^2) 27.71MB
class Solution {
public:int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {unordered_map<int, int> hashmap;int count = 0;for (int num1 : nums1) {for (int num2 : nums2) {hashmap[num1 + num2]++;}}for (int num3 : nums3) {for (int num4 : nums4) {int diff = 0 - num3 - num4;if (hashmap.find(diff) != hashmap.end()) count += hashmap[diff];}}return count;}
};

本文参考了 LeetCode官方题解 及 代码随想录


http://www.ppmy.cn/ops/153488.html

相关文章

LiteFlow Spring boot使用方式

文章目录 概述LiteFlow框架的优势规则调用逻辑规则组件定义组件内数据获取通过 DefaultContext自定义上下文 通过 组件规则定义数据通过预先传入数据 liteflow 使用 概述 在每个公司的系统中&#xff0c;总有一些拥有复杂业务逻辑的系统&#xff0c;这些系统承载着核心业务逻…

数据结构——实验八·学生管理系统

嗨~~欢迎来到Tubishu的博客&#x1f338;如果你也是一名在校大学生&#xff0c;正在寻找各种编程资源&#xff0c;那么你就来对地方啦&#x1f31f; Tubishu是一名计算机本科生&#xff0c;会不定期整理和分享学习中的优质资源&#xff0c;希望能为你的编程之路添砖加瓦⭐&…

PostgreSQL数据库的运行机制和架构体系

PostgreSQL数据库的运行机制和架构体系 PostgreSQL架构&#xff1a; Postmaster&#xff1a;主进程&#xff0c;负责启动其他所有进程。 BackendProcesses&#xff1a;后端进程&#xff0c;每个客户端连接会启动一个新的后端进程来处理查询。 SharedBuffers&#xff1a;共享缓…

WebSocket异步导出

WebSocket异步导出 1、安装sockjs-client和stompjs2、连接后台3、vite.config.ts 配置反向代理4、导出并实时通信5、 封装WebSocket 文件注册登录(城通网盘) 1、安装sockjs-client和stompjs import SockJS from sockjs-client/dist/sockjs.min.js import Stomp from stompjs2、…

IGBT的损耗计算的学习【2025/1/24】

可以通过示波器实测IGBT电压电流波形&#xff0c;然后通过示波器的math功能将电压电流波形乘积后积分求损耗。 软开管&#xff1a;给了导通信号&#xff0c;但是电流并没有从此IGBT流过 IGBT&#xff08;绝缘栅双极晶体管&#xff09;的损耗主要分为 导通损耗 和 开关损耗 两部…

Java 大视界 -- Java 大数据在元宇宙中的关键技术与应用场景(65)

&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎来到 青云交的博客&#xff01;能与诸位在此相逢&#xff0c;我倍感荣幸。在这飞速更迭的时代&#xff0c;我们都渴望一方心灵净土&#xff0c;而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识&#xff0c;也…

在排序数组中查找元素的第一个和最后一个位置

给你一个按照非递减顺序排列的整数数组 nums&#xff0c;和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。 如果数组中不存在目标值 target&#xff0c;返回 [-1, -1]。 你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。 示例 1&#xff1a…

蓝桥杯例题三

无论前方困难如何重重&#xff0c;我们都要坚定信念&#xff0c;勇往直前。面对挑战和困境&#xff0c;不要退缩&#xff0c;不要放弃&#xff0c;要坚持走下去。当我们感到疲惫时&#xff0c;要告诉自己&#xff1a;“我可以&#xff0c;我一定行&#xff01;”相信自己的实力…