哈希表 - 代码随想录

哈希表 - 代码随想录
Mr.L哈希表
242. 有效的字母异位词
问题描述
给定两个字符串
s和t,编写一个函数来判断t是否是s的 字母异位词。
示例 1:
1 | 输入: s = "anagram", t = "nagaram" |
示例 2:
1 | 输入: s = "rat", t = "car" |
提示:
1 <= s.length, t.length <= 5 * 104s和t仅包含小写字母
进阶: 如果输入字符串包含 unicode 字符怎么办?你能否调整你的解法来应对这种情况?
思路
数组其实就是一个简单哈希表,且这道题目中字符串只有小写字符,那么就可以定义一个大小为 26 的数组(字符a到字符z的ASCII是26个连续的数值),记录字符串s里字符出现的次数。
遍历字符串 s 时,将 s[i] - ‘a’ 所在的元素做+1 操作即可;
同样遍历字符串 t 时,将 t[i] - ‘a’ 所在的元素做-1 操作即可;
数组若存在元素不为0,说明字符串s和t一定是谁多了字符或者谁少了字符,return false;
反之,数组所有元素都为0,说明字符串s和t是字母异位词,return true。
代码
1 | class Solution { |
349. 两个数组的交集
问题描述
给定两个数组
nums1和nums2,返回 它们的 交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。
示例 1:
1 | 输入:nums1 = [1,2,2,1], nums2 = [2,2] |
示例 2:
1 | 输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4] |
提示:
1 <= nums1.length, nums2.length <= 10000 <= nums1[i], nums2[i] <= 1000
思路
如果哈希值比较少、特别分散、跨度非常大,使用数组就造成空间的极大浪费。
当题目限制了数值的大小,可以使用数据来做哈希表;但当题目没有限制数值的大小,就无法使用数组来做哈希表了。
题目中描述 输出结果中的每个元素是唯一的,也就是说输出的结果的去重的, 同时可以不考虑输出结果的顺序 ,这里推荐使用unordered_set ,因为它读写效率是最高的,且不需要对数据进行排序,数据也不重复。
代码
1 | class Solution { |
202. 快乐数
问题描述
编写一个算法来判断一个数
n是不是快乐数。「快乐数」 定义为:
- 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
- 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
- 如果这个过程 结果为 1,那么这个数就是快乐数。
如果
n是 快乐数 就返回true;不是,则返回false。
示例 1:
1 | 输入:n = 19 |
示例 2:
1 | 输入:n = 2 |
提示:
1 <= n <= 231 - 1
思路
在求和的过程中,sum会重复出现,这对解题很重要!
因此,当我们遇到要快速判断一个元素是否出现集合里的时候,就要考虑哈希法。判断这个sum是否重复出现,
判断sum是否重复出现就可以使用unordered_set,如果重复了就是return false, 否则一直找到sum为1为止。
代码
1 | class Solution { |
1. 两数之和
问题描述
给定一个整数数组
nums和一个整数目标值target,请你在该数组中找出 和为目标值target的那 两个 整数,并返回它们的数组下标。你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。
你可以按任意顺序返回答案。
示例 1:
1 | 输入:nums = [2,7,11,15], target = 9 |
示例 2:
1 | 输入:nums = [3,2,4], target = 6 |
示例 3:
1 | 输入:nums = [3,3], target = 6 |
提示:
2 <= nums.length <= 104-109 <= nums[i] <= 109-109 <= target <= 109- 只会存在一个有效答案
**进阶:**你可以想出一个时间复杂度小于 O(n2) 的算法吗?
思路
首先,我们先来看一下使用数组和 set 来做哈希法的局限:
- 数组的大小受限制,如果元素很少,而哈希值太大会造成内存空间的浪费;
- set是一个集合,里面只能放一个key,而两数之和这道题目,不仅要判断 y 是否存在而且还要记录 y 的下标位置,因为要返回 x 和 y的下标。所以set 也不能用。
在遍历数组的时候,使用 {key:数据元素,value:数组元素对应的下标或其他值} 的存储结构,只需要向 map 去查询是否有和目前遍历元素匹配的数值,如果有,就找到的匹配对,如果没有,就把目前遍历的元素放进map中,因为 map 存放的就是我们访问过的元素。
代码
1 | class Solution { |
454. 四数相加 II
问题描述
给你四个整数数组
nums1、nums2、nums3和nums4,数组长度都是n,请你计算有多少个元组(i, j, k, l)能满足:
0 <= i, j, k, l < nnums1[i] + nums2[j] + nums3[k] + nums4[l] == 0
示例 1:
1 | 输入:nums1 = [1,2], nums2 = [-2,-1], nums3 = [-1,2], nums4 = [0,2] |
示例 2:
1 | 输入:nums1 = [0], nums2 = [0], nums3 = [0], nums4 = [0] |
提示:
n == nums1.lengthn == nums2.lengthn == nums3.lengthn == nums4.length1 <= n <= 200-228 <= nums1[i], nums2[i], nums3[i], nums4[i] <= 228
思路
本题解题步骤:
- 首先定义 一个 unordered_map,key放 nums1 和 nums2 两数之和,value 放 nums1 和 nums2 两数之和出现的次数;
- 遍历 nums1 和 nums2 数组,统计两个数组元素之和,和出现的次数,放到 map 中;
- 定义 int 变量 cnt,统计 a+b+c+d = 0 出现的次数;
- 再遍历 nums3 和 nums4 数组,如果 0-(c+d) 在map中出现过,就用 cnt 把 map 中 key 对应的 value, 即出现次数统计出来。
- 最后返回统计值 cnt 就可以了;
代码
1 | class Solution { |
383. 赎金信
问题描述
给你两个字符串:
ransomNote和magazine,判断ransomNote能不能由magazine里面的字符构成。如果可以,返回
true;否则返回false。
magazine中的每个字符只能在ransomNote中使用一次。
示例 1:
1 | 输入:ransomNote = "a", magazine = "b" |
示例 2:
1 | 输入:ransomNote = "aa", magazine = "ab" |
示例 3:
1 | 输入:ransomNote = "aa", magazine = "aab" |
提示:
1 <= ransomNote.length, magazine.length <= 105ransomNote和magazine由小写英文字母组成
思路
本题重点在于字符串 magazines能否组成字符串 ransom,而不用管字符串 ransom 能不能组成字符串 magazines。
因为题目说只有小写字母,可采用空间换取时间的哈希策略,用一个长度为26的数组来记录magazine里字母出现的次数。
然后再用 ransomNote 去验证这个数组是否包含了 ransomNote 所需要的所有字母。
但是可能会想:用数组干啥,都用map完事了
在本题的情况下,使用map的空间消耗要比数组大一些,因为map要维护红黑树或者哈希表,而且还要做哈希函数,是费时的!数据量大的话就能体现出来差别了。 所以数组更加简单直接有效!
代码
1 | class Solution { |
15. 三数之和
问题描述
给你一个整数数组
nums,判断是否存在三元组[nums[i], nums[j], nums[k]]满足i != j、i != k且j != k,同时还满足nums[i] + nums[j] + nums[k] == 0。请你返回所有和为0且不重复的三元组。**注意:**答案中不可以包含重复的三元组。
示例 1:
1 | 输入:nums = [-1,0,1,2,-1,-4] |
示例 2:
1 | 输入:nums = [0,1,1] |
示例 3:
1 | 输入:nums = [0,0,0] |
提示:
3 <= nums.length <= 3000-105 <= nums[i] <= 105
思路
这道题目使用双指针法要比哈希法高效一些
- 首先将数组排序,然后有一层 for 循环,
i从下标 0 的地方开始,定义一个下标left定义在i+1的位置上,定义下标right在数组结尾的位置上。 - 在数组中找到nums[i] + nums[left] + nums[right] = 0;
- 如果nums[i] + nums[left] + nums[right] > 0, 说明此时三数之和大了,因为数组是排序后的,所以
right下标应该向左移动,这样才能让三数之和小一些。 - 如果 nums[i] + nums[left] + nums[right] < 0 说明 此时 三数之和小了,
left就向右移动,才能让三数之和大一些,直到left与right相遇为止。
代码
1 | class Solution { |
- 时间复杂度: O(n^2)
- 空间复杂度: O(1)
18.四数之和
问题描述
给你一个由
n个整数组成的数组nums,和一个目标值target。请你找出并返回满足下述全部条件且不重复的四元组[nums[a], nums[b], nums[c], nums[d]](若两个四元组元素一一对应,则认为两个四元组重复):
0 <= a, b, c, d < na、b、c和d互不相同nums[a] + nums[b] + nums[c] + nums[d] == target你可以按 任意顺序 返回答案 。
示例 1:
1 | 输入:nums = [1,0,-1,0,-2,2], target = 0 |
示例 2:
1 | 输入:nums = [2,2,2,2,2], target = 8 |
提示:
1 <= nums.length <= 200-109 <= nums[i] <= 109-109 <= target <= 109
思路
和三数之和思路相同,但是有一些细节需要注意,例如:
- 不要判断
nums[k] > target就返回了,三数之和 可以通过nums[i] > 0就返回了,因为 0 已经是确定的数了,四数之和这道题目 target是任意值。比如:数组是[-4, -3, -2, -1],target是-10,不能因为-4 > -10而跳过。但依旧可以做剪枝,逻辑变成nums[k] > target && (nums[k] >=0 || target >= 0)就可以了;
四数之和的双指针解法是两层for循环nums[k] + nums[i]为确定值,依然是循环内有left和right下标作为双指针,找出nums[k] + nums[i] + nums[left] + nums[right] == target的情况,三数之和的时间复杂度是O(n^2),四数之和的时间复杂度是O(n^3) 。
那么一样的道理,五数之和、六数之和等等都采用这种解法。
代码
1 | class Solution { |
- 时间复杂度: O(n^3)
- 空间复杂度: O(1)


