本文整理常见的哈希表题目,按题目编号记录题意、示例、限制条件和解答代码。
相关 Python 知识点
列表
list[]- 和 C 语言里的数组类似,支持动态扩容,可修改元素,有序的,可重复。
- 添加元素使用
list.append(value) - 删除元素使用
list.remove(value)或del list[index]。 - 可作栈使用,使用
list.append(value)入栈,list.pop()出栈。 - 访问方式为
list[index],索引从0开始。 in操作可用于判断元素是否在列表中,时间复杂度为 $O(n)$。- 切片操作
list[start:end],返回从start到end-1的子列表。list[:end],返回从索引0到end-1的子列表。list[start:],返回从索引start到列表末尾的子列表。list[:],返回整个列表的副本。
集合
set{}- 和数学中的集合类似,元素唯一,无序的。
- 添加元素使用
set.add(value)。 - 删除元素使用
set.remove(value)或set.discard(value)。 - 访问方式为
value in set,判断元素是否在集合中,平均时间复杂度为 $O(1)$。
字典
dict{}- 添加或修改元素使用
dict[key] = value。 - 删除元素使用
del dict[key]。 - 访问方式为
dict[key],获取键对应的值,平均时间复杂度为 $O(1)$。
- 添加或修改元素使用
元组
tuple()- 和列表类似,但不可修改元素,有序的,可重复。
- 访问方式为
tuple[index],索引从0开始。 in操作可用于判断元素是否在元组中,时间复杂度为 $O(n)$。- 元组可作为字典的键或集合的元素,而列表不可。
题目索引
| 题号 | 题目 | 难度 |
|---|---|---|
| 1 | 两数之和 | 简单 |
| 49 | 字母异位词分组 | 中等 |
| 128 | 最长连续序列 | 中等 |
| 349 | 两个数组的交集 | 简单 |
| 202 | 快乐数 | 简单 |
| 15 | 三数之和 | 中等 |
| 383 | 赎金信 | 简单 |
| 454 | 四数相加 II | 中等 |
两数之和
题号: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 <= 10^4-10^9 <= nums[i] <= 10^9-10^9 <= target <= 10^9- 只会存在一个有效答案
解答
思路
最直接的做法是使用两个 for 循环进行暴力枚举。
为了降低时间复杂度,可以将“寻找两个数的和”转换为“寻找当前数字的补数”。对于当前数字 num,需要查找的补数为 target - num。
遍历数组时,使用字典记录已经遍历过的数字及其下标,就可以在平均 $O(1)$ 的时间内判断补数是否存在。
暴力解法 $O(n^2)$
1 | from typing import List |
时间复杂度为 $O(n^2)$,空间复杂度为 $O(1)$。
哈希表解法 $O(n)$
1 | from collections import defaultdict |
时间复杂度为 $O(n)$,空间复杂度为 $O(n)$。
字母异位词分组
题号:49
难度:中等
题目描述
给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。
示例
示例 1:
1 | 输入:strs = ["eat", "tea", "tan", "ate", "nat", "bat"] |
解释:
- 在 strs 中没有字符串可以通过重新排列来形成 “bat”。
- 字符串 “nat” 和 “tan” 是字母异位词,因为它们可以重新排列以形成彼此。
- 字符串 “ate”、”eat” 和 “tea” 是字母异位词,因为它们可以重新排列以形成彼此。
示例 2:
1 | 输入:strs = [""] |
示例 3:
1 | 输入:strs = ["a"] |
提示
1 <= strs.length <= 10^40 <= strs[i].length <= 100strs[i]仅包含小写字母
解答
思路
暴力解法就是将 n 个字符串进行排序,排序后等价的异位词会得到相同的排序结果(key),然后将相同的排序结果归为一组(value)。
因为暴力解法的排序带来了 k log k 的复杂度,可以想办法优化构造 key 的过程。我们可以利用统计的做法,构造一个长度为 26 的特征数组,每一位代表一个字母,用这个数组作为 key,这样对于每个字符串只需要 k 的时间复杂度即可。
暴力解法 $O(n k \log k)$
1 | from collections import defaultdict |
最优解 $O(n k)$
1 | from collections import defaultdict |
最长连续序列
题号:128
难度:中等
题目描述
给定一个未排序的整数数组 nums,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
请你设计并实现时间复杂度为 $O(n)$ 的算法解决此问题。
示例
示例 1:
1 | 输入:nums = [100,4,200,1,3,2] |
示例 2:
1 | 输入:nums = [0,3,7,2,5,8,4,6,0,1] |
示例 3:
1 | 输入:nums = [1,0,1,2] |
提示
0 <= nums.length <= 10^5-10^9 <= nums[i] <= 10^9
解答
思路
暴力解法很简单,给定的数组没有排序,先排序后进行一次遍历即可,也就是 n log n + n 的时间复杂度。
最优解法是,找一个连续的序列首先得找到一个起点,然后从这个起点开始以 1 步进查找序列下一个数字是否在数组里。不断查找这一步要用 dict 来进行优化,最后就能得到一个 $O(n)$ 的时间复杂度。
暴力解法 $O(n \log n)$
1 | from typing import List |
最优解法 $O(n)$
1 | from typing import List |
两个数组的交集
题号: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
解答
思路
先分别使用 set 对两个数组去重,然后遍历 nums1_set,检查当前元素是否存在于 nums2_set 中。
判断元素是否存在时使用集合的 in 操作,平均时间复杂度为 $O(1)$;如果直接在列表中查找,时间复杂度会增加到 $O(n)$。
1 | from typing import List |
快乐数
题号:202
难度:简单
题目描述
编写一个算法来判断一个数 n 是不是快乐数。
“快乐数” 定义如下:
- 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
- 然后重复这个过程直到这个数变为
1,也可能会无限循环但始终变不到1。 - 如果这个过程的结果为
1,那么这个数就是快乐数。 - 如果
n是快乐数就返回true,否则返回false。
示例
示例 1:
1 | 输入:n = 19 |
示例 2:
1 | 输入:n = 2 |
提示
1 <= n <= 2^31 - 1
解答
这个题其实不难,主要是要知道如果不是快乐数,一定会进入循环。所以我们用一个集合set保存出现过的数字,如果出现过的数再次出现,就说明进入循环了,返回False。
那么为啥一定会循环呢,因为每一位数字的取值是0到9,平方后每一位的取值是0到81,假设n有k位,那么平方和的最大值就是k81。对于一个k位数,k81的最大值是9*81=729,所以平方和的结果一定会小于等于729;而且平方和的结果是整数,取值范围有限,所以如果不是快乐数,一定会进入循环。
1 | from typing import List |
三数之和
题号: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-10^5 <= nums[i] <= 10^5
解答
思路
三数之和可以拆解为两数之和:先固定一个数 nums[i],然后在后面的元素中寻找两个数,使它们的和等于 -nums[i]。
对于固定的 nums[i],使用集合 seen 保存已经遍历过的数字。遍历当前数字 nums[j] 时,只需要判断它的补数 -nums[i] - nums[j] 是否存在于 seen 中。
本题要求的是不重复的数值三元组,而不是所有合法的下标组合。因此,将找到的三元组排序后转为元组,并存入集合 results,即可自动去重。
哈希表解法 $O(n^2)$
这份哈希表解法的时间复杂度为 $O(n^2)$,但在 LeetCode 上可能超时。这道题通常使用排序加双指针的方法进一步优化。
排序加左右指针的优化版本请参考:双指针:三数之和。
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 <= 10^5ransomNote和magazine由小写英文字母组成
解答
思路
有点像同字母异位词的做法,只要统计 magazine 中每个字母的数量,然后遍历 ransomNote,每遇到一个字母就将对应的数量减一,如果减到小于零,就说明 magazine 中没有足够的该字母,返回 False。
1 | class Solution: |
四数相加 II
题号:454
难度:中等
题目描述
给你四个整数数组 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-2^28 <= nums1[i], nums2[i], nums3[i], nums4[i] <= 2^28
解答
思路
将四个数组分成两组,组合成左边数字和右边数字,然后用两数之和的思路做就好了
1 | from collections import defaultdict |