目录
哈希表理论基础
一些点
242.有效的字母异位词
题目
思路
349. 两个数组的交集
题目
思路1使用字典和集合
思路2使用集合
202. 快乐数
题目
思路
1. 两数之和
题目
思路
建议:大家要了解哈希表的内部实现原理,哈希函数,哈希碰撞,以及常见哈希表的区别,数组,set 和map。
什么时候想到用哈希法,当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法。 这句话很重要,大家在做哈希表题目都要思考这句话。
文章讲解:代码随想录
一般哈希表都是用来快速判断一个元素是否出现集合里
当我们想使用哈希法来解决问题的时候,我们一般会选择如下三种数据结构。
建议: 这道题目,大家可以感受到 数组 用来做哈希表 给我们带来的遍历之处。
题目链接/文章讲解/视频讲解: 代码随想录
给定两个字符串 s
和 t
,编写一个函数来判断 t
是否是 s
的 字母异位词。(字母异位词是通过重新排列不同单词或短语的字母而形成的单词或短语,并使用所有原字母一次。例如,"listen" 和 "silent" 就是一对字母异位词)
示例 1:
输入: s = "anagram", t = "nagaram" 输出: true
示例 2:
输入: s = "rat", t = "car" 输出: false
提示:
1 <= s.length, t.length <= 5 * 104
s
和 t
仅包含小写字母进阶: 如果输入字符串包含 unicode 字符怎么办?你能否调整你的解法来应对这种情况?
数组其实就是一个简单哈希表,而且这道题目中字符串只有小写字符,那么就可以定义一个数组,来记录字符串s里字符出现的次数
定一个数组叫做record,大小为26 就可以了,初始化为0
通过统计每个字符出现的次数来判断两个字符串是否为字母异位词
ord()
函数返回一个字符的 Unicode 码点(整数表示)当我们需要统计小写字母的出现次数时,可以利用这个表达式将字母映射到数组的索引位置:
- 对于字母 'a':
ord('a') - ord('a') = 97 - 97 = 0
,对应数组索引 0- 对于字母 'b':
ord('b') - ord('a') = 98 - 97 = 1
,对应数组索引 1- 依此类推,直到字母 'z' 对应数组索引 25(因为
ord('z') - ord('a') = 122 - 97 = 25
)这样,26 个小写字母刚好可以映射到长度为 26 的数组中。
初始化计数器数组:
record = [0] * 26
创建一个长度为 26 的数组,用于记录每个字母出现的次数。数组的索引对应字母表中的字母(0 对应 'a',1 对应 'b',依此类推)。
遍历字符串 s
中的每个字符,计算其相对于 'a' 的位置(例如,'a' 对应 0,'b' 对应 1,依此类推),并将对应位置的计数器加 1。
抵消第二个字符串的字符频率:
for i in t:
record[ord(i) - ord("a")] -= 1
遍历字符串 t
中的每个字符,同样计算其相对于 'a' 的位置,并将对应位置的计数器减 1。如果两个字符串是字母异位词,那么这一步之后所有计数器应该都回到 0。
检查计数器数组:
for i in range(26):
if record[i] != 0:
return False
return True
遍历计数器数组,如果发现任何一个位置的计数器不为 0,说明两个字符串的字符频率不匹配,返回 False
。如果所有计数器都为 0,说明两个字符串是字母异位词,返回 True
。
class Solution:
def isAnagram(self, s: str, t: str) -> bool:
record = [0] * 26
for i in s:
record[ord(i) - ord("a")] += 1
for i in t:
record[ord(i) - ord("a")] -= 1
for i in range(26):
if record[i] != 0:
return False
return True
# 测试用例
test_cases = [
("listen", "silent", True), # 有效异位词
("hello", "bello", False), # 无效异位词
("triangle", "integral", True),
("apple", "appla", False),
("abc", "abcd", False), # 长度不同
("", "", True), # 空字符串
]
# 执行测试
solution = Solution()
for s, t, expected in test_cases:
result = solution.isAnagram(s, t)
status = "✓" if result == expected else "✗"
print(f"{status} Test: '{s}' vs '{t}' → Expected: {expected}, Got: {result}")
建议:本题就开始考虑 什么时候用set 什么时候用数组,本题其实是使用set的好题,但是后来力扣改了题目描述和 测试用例,添加了 0 <= nums1[i], nums2[i] <= 1000 条件,所以使用数组也可以了,不过建议大家忽略这个条件。 尝试去使用set。
题目链接/文章讲解/视频讲解:代码随想录
给定两个数组 nums1
和 nums2
,返回 它们的 交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。
示例 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
- 使用字典判断元素出现的次数
- 使用集合将不重复的元素储存起来
- 检查交集
table = {}
for num in nums1:
table[num] = table.get(num, 0) + 1
table[num] = table.get(num, 0) + 1的意思是:
if num in table:
table[num] += 1 # 键已存在,计数加1
else:
table[num] = 1 # 键不存在,初始化为1
table.get(num, 0)
get()
是字典(哈希表)的方法,用于获取指定键的值。它接收两个参数:
get(键, 默认值)
。如果键
num
存在于字典中,返回对应的值;如果不存在,则返回默认值0
。
table={}
for num in nums1:
table[num]=table.get(num,0)+1
res=[]
for num in nums2:
if num in table:
res.append(num)
del table[num]#遍历到num2中的重复元素时就不会多次触发if num in table
return list(res)
上面是给的参考,可以这样改:
table={}
for num in nums1:
table[num]=1#确认存在就行不用计数
res=set()
for num in table:#遍历table检查是否在num2中,这样避免遍历num2中的重复元素
if num in nums2:
res.add(num)#不用删除table中的元素
return list(res)
方法 | 适用对象 | 元素是否可重复 | 顺序是否保留 | 元素类型限制 |
---|---|---|---|---|
.add() |
集合(Set) | 自动去重 | 无序 | 元素必须可哈希 |
.append() |
列表(List) | 允许重复 | 按插入顺序 | 无特殊限制 |
s = set()
s.add(1) # 添加元素 1
s.add(2) # 添加元素 2
s.add(2) # 重复添加,集合仍为 {1, 2}
s.add((3, 4)) # 元组可哈希,添加成功
# s.add([5, 6]) # 列表不可哈希,报错:TypeError
print(s) # 输出: {1, 2, (3, 4)}
lst = []
lst.append(1) # 添加元素 1
lst.append(2) # 添加元素 2
lst.append(2) # 重复添加,列表变为 [1, 2, 2]
lst.append([3, 4]) # 添加列表作为元素
print(lst) # 输出: [1, 2, 2, [3, 4]]
数据结构 | 添加方法 | 特点 |
---|---|---|
列表(List) | .append() |
在尾部添加单个元素。 |
列表(List) | .extend() |
用可迭代对象扩展列表(如合并列表)。 |
元组(Tuple) | 不可添加 | 元组不可变,需创建新元组(如 t = t + (1,) )。 |
字典(Dict) | d[key] = val |
通过键值对添加或更新元素。 |
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
return list(set(nums1) & set(nums2))
建议:这道题目也是set的应用,其实和上一题差不多,就是 套在快乐数一个壳子
题目链接/文章讲解:代码随想录
编写一个算法来判断一个数 n
是不是快乐数。
「快乐数」 定义为:
如果 n
是 快乐数 就返回 true
;不是,则返回 false
。
示例 1:
输入:n = 19 输出:true 解释: 12 + 92 = 82 82 + 22 = 68 62 + 82 = 100 12 + 02 + 02 = 1
示例 2:
输入:n = 2 输出:false
提示:
1 <= n <= 231 - 1
题目中说了会 无限循环,那么也就是说求和的过程中,sum会重复出现,这对解题很重要!
这道题目使用哈希法,来判断这个sum是否重复出现,如果重复了就是return false, 否则一直找到sum为1为止
使用集合,时间复杂度为O(1),数组为O(n),集合更合适
将这个数字n转变成字符串str,遍历每个元素,判断元素平方和是否重复
class Solution:
def isHappy(self, n: int) -> bool:
# 使用集合记录已经出现过的数字,用于检测循环
# 集合的查找效率为O(1),比列表更适合快速判断元素是否存在
seen = set()
# 当n不等于1时,持续计算各位数字的平方和
while n != 1:
# 计算当前数字n的各位数字平方和
# 例如:n=19 → str(n)='19' → 遍历 '1' 和 '9' → 计算 1² + 9² = 82
n = sum(int(i) ** 2 for i in str(n))
# 如果当前计算结果已经在集合中出现过,说明进入了循环,不是快乐数
if n in seen:
return False
# 将当前计算结果加入集合,标记为已出现
seen.add(n)
# 当n最终变为1时,说明是快乐数,返回True
return True
测试代码
def test_is_happy():
solution = Solution()
# 测试快乐数
assert solution.isHappy(19) is True, "19是快乐数"
assert solution.isHappy(7) is True, "7是快乐数"
# 测试非快乐数
assert solution.isHappy(4) is False, "4不是快乐数"
assert solution.isHappy(11) is False, "11不是快乐数"
# 测试边界值
assert solution.isHappy(1) is True, "1本身是快乐数"
print("所有测试用例通过!")
test_is_happy()
建议:本题虽然是 力扣第一题,但是还是挺难的,也是 代码随想录中 数组,set之后,使用map解决哈希问题的第一题。
建议大家先看视频讲解,然后尝试自己写代码,在看文章讲解,加深印象。
题目链接/文章讲解/视频讲解:代码随想录
给定一个整数数组 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
-109 <= nums[i] <= 109
-109 <= target <= 109
进阶:你可以想出一个时间复杂度小于 O(n2)
的算法吗?
- 哈希表查找优化:使用字典(哈希表)记录已遍历元素及其下标,将查找补数的时间复杂度从 O (n) 降至 O (1)。
- 单次遍历:在遍历数组的过程中,边检查补数是否存在,边将当前元素存入字典,确保每个元素只被处理一次。
流程
- 初始状态:
records={}
- 遍历第一个元素:
value=2
,计算补数:9-2=7
- 检查
7
是否在records
中(不在),将2:0
存入records
→records={2:0}
- 遍历第二个元素:
value=7
,计算补数:9-7=2
- 检查
2
是否在records
中(存在),返回[records[2], 1]
→[0, 1]
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
# 使用字典记录已遍历元素及其下标,键为元素值,值为元素下标
records = dict()
# 遍历数组中的每个元素及其下标
for index, value in enumerate(nums):
# 计算当前元素与目标值的差值
complement = target - value
# 检查差值是否已存在于字典中(即是否之前遍历过)
if complement in records:
# 如果存在,说明找到了两个数的和等于目标值
# 返回差值的下标(即records[complement])和当前元素的下标
return [records[complement], index]
# 如果差值不存在于字典中,将当前元素及其下标存入字典
# 这样后续遍历其他元素时,可以快速查找该元素是否是所需的补数
records[value] = index
# 如果遍历完整个数组都没有找到符合条件的两个数,返回空列表
return []