leetcode刷题之哈希表篇
本次学习主要参考文章:LeetCode刷题之哈希表篇——从入门到进阶。使用编程语言:Python3
哈希表是一种根据键(Key)直接访问内存存储位置的数据结构。通过计算一个关于键值的函数,将所需查询的数据映射到表中一个位置来访问记录,从而加快查找速度。这个映射函数称为哈希函数。
简单来说,哈希表是根据关键码的值而直接进行访问的数据结构。
一、判断元素是否存在或统计频率
242. 有效的字母异位词
给定两个字符串
s和t,编写一个函数来判断 t 是否是 s 的 字母异位词。
字母异位词:字母的种类和数量完全一致。所以:
- 使用一个哈希表来统计第一个字符串
s中所有字母出现的次数(题目中限定t和s中只包含小写字母,所以可以建立一个长度为26的频次数组)。 - 对字符串
t进行遍历,每遇到一个字母,就在哈希表中把对应字母的计数减一。如果减一后发现计数小于0了,说明t里包含了s里没有的、或者数量更多的字母。
class Solution:
def isAnagram(self, s: str, t: str) -> bool:
if len(s) != len(t):
return False
table = [0] * 26
# 统计s中各字母出现的频次
for i in s:
table[ord(i) - 97] += 1 # a的ASCII码值是97
for j in t:
table[ord(j) - 97] -= 1
if table[ord(j) - 97] < 0:
return False
return True
别的方法:异位词的字母种类和数量完全一致,所以排序后应该是相等的
class Solution:
def isAnagram(self, s: str, t: str) -> bool:
if sorted(s)==sorted(t):
return True
else:
return False
202. 快乐数
编写一个算法来判断一个数 n 是不是快乐数。
「快乐数」 定义为:
- 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
- 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
- 如果这个过程 结果为 1,那么这个数就是快乐数。
如果 n 是 快乐数 就返回 true ;不是,则返回 false 。
无限循环:如果在计算过程中,某个数字重复出现了,就说明它不可能是快乐数。
class Solution:
def isHappy(self, n: int) -> bool:
has_list = [] # 用哈希表来存储出现过的数字
cnd = 1 # 循环开始条件
while cnd:
has_list.append(n)
new = 0
for i in str(n):
new += int(i)**2
if new == 1:
return True
if new in has_list:
return False
n = new
二、统计与去重
349. 两个数组的交集
给定两个数组
nums1和nums2,返回 它们的 交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
nums1_st = set(nums1)
nums2_st = set(nums2)
list = []
for i in nums1_st:
if i in nums2_st:
list.append(i)
return list
350. 两个数组的交集 II
给你两个整数数组
nums1和nums2,请你以数组形式返回两数组的交集。返回结果中每个元素出现的次数,应与元素在两个数组中都出现的次数一致(如果出现次数不一致,则考虑取较小值)。可以不考虑输出结果的顺序。
这里要考虑每个元素重复的次数,所以我们可以用一个哈希表来存储nums1中每个元素出现的次数,然后遍历nums2的每个元素,一旦出现就在频次表中减一。如果该元素在哈希表中的频次大于0,则加入到交集中。
from collections import defaultdict
class Solution:
def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:
dict = defaultdict(int) # 创建一个defaultdict,默认值为整数0
list = []
for i in nums1:
dict[i] += 1
for j in nums2:
dict[j] -= 1
if dict[j] < 0:
continue
else:
list.append(j)
return list
更加直观的解法:
class Solution:
def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:
list = []
for i in nums1: # 对nums1中的每个元素进行遍历
if i in nums2: # 如果这个元素也在nums2中,添加到交集中,并删除nusm2中的第一个该元素,避免重复计数。
list.append(i)
nums2.remove(i)
return list
三、分组与优化
49. 字母异位词分组
给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。
字母异位词:字母异位词是通过重新排列不同单词或短语的字母而形成的单词或短语,并使用所有原字母一次。
两个字符串互为字母异位词,当且仅当两个字符串包含的字母相同。同一组字母异位词中的字符串具备相同点,可以使用相同点作为一组字母异位词的标志,使用哈希表存储每一组字母异位词,哈希表的键为一组字母异位词的标志,哈希表的值为一组字母异位词列表。
所以,该题的重点是找到哈希表的键,这里使用两种方法:
方法一:排序
- 对于任意一个异位词组合(比如 “eat”, “tea”, “ate”),虽然字母顺序不同,但把它们排序后,都会得到同一个字符串 “aet”。这个排序后的字符串,就是它们的“基因”。
- 我们可以创建一个
dict,用这个“基因”(排序后的字符串)作为key,用一个数组来存储所有拥有这个基因的字符串作为value。遍历输入的数组,对每个字符串都计算出它的“基因”,然后把它塞进dict中对应的分组里。
from collections import defaultdict
class Solution:
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
'''
对于任意一个异位词组合(比如 "eat", "tea", "ate"),虽然字母顺序不同,但把它们排序后,都会得到同一个字符串 "aet"。这个排序后的字符串,就是它们的“基因”
'''
dict = defaultdict(list)
# 建立”基因“表。每个”基因“作为键,对应基因的字符串作为值
for i in range(len(strs)):
temp = str(sorted(strs[i]))
dict[temp].append(strs[i])
return list(dict.values())
方法二:计数
- 由于互为字母异位词的两个字符串包含的字母相同,因此两个字符串中的相同字母出现的次数一定是相同的,故可以将每个字母出现的次数使用字符串表示,作为哈希表的键。
- 由于字符串只包含小写字母,因此对于每个字符串,可以使用长度为 26 的数组记录每个字母出现的次数。
from collections import defaultdict
class Solution:
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
dict = defaultdict(list)
for i in strs:
counts = [0] * 26
for j in i: # 计数
counts[ord(j) - ord('a')] += 1
# 需要将 list 转换成 tuple 才能进行哈希
dict[tuple(counts)].append(i)
return list(dict.values())
15. 三数之和
给你一个整数数组
nums,判断是否存在三元组[nums[i], nums[j], nums[k]]满足i != j、i != k且j != k,同时还满足 nums[i] + nums[j] + nums[k] == 0。请你返回所有和为0且不重复的三元组。
注意:答案中不可以包含重复的三元组。
常见思路:先固定一个数,然后把问题转为求两数之和。
这里使用排序+双指针的方式。
当我们需要枚举数组中的两个元素时,如果我们发现随着第一个元素的递增,第二个元素是递减的,那么就可以使用双指针的方法,将枚举的时间复杂度从
O
(
N
2
)
O(N^2)
O(N2) 减少至
O
(
N
)
O(N)
O(N) 。为什么是
O
(
N
)
O(N)
O(N) 呢?这是因为在枚举的过程每一步中,「左指针」会向右移动一个位置(也就是题目中的
b
b
b),而「右指针」会向左移动若干个位置,这个与数组的元素有关,但我们知道它一共会移动的位置数为
O
(
N
)
O(N)
O(N),均摊下来,每次也向左移动一个位置,因此时间复杂度为
O
(
N
)
O(N)
O(N)。
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
n = len(nums)
if n <= 2:
return []
nums.sort() # 排序
list1 = []
ans = 1
# 建立哈希表,键是满足条件的三个数组成的字符串,值是这三个数组成的列表。这样可以避免出现重复的三元组
dict = {}
for i in range(n-2): # 先固定一个数
# 只有和上一次的数字不同时,才会进行枚举
if i == 0 or nums[i] != nums[i-1]:
left = i + 1
right = n - 1
while left < right:
# 当前三个数的和
ans = nums[i] + nums[left] + nums[right]
# 如果ans小于0,只能移动左指针才有可能使ans等于0
if ans < 0:
left += 1
# 如果ans大于0,只能移动右指针才有可能使ans等于0
elif ans > 0:
right -= 1
# ans等于0,这三个数满足条件,加到dict中
else:
dict[str([nums[i], nums[left], nums[right]])] = [nums[i], nums[left], nums[right]]
left += 1
right -= 1
continue
return list(dict.values())
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你可以按 任意顺序 返回答案 。
和三数之和一个思路,排序+双指针。
class Solution:
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
n = len(nums)
if n < 4: return []
# 依次固定两个数,剩下的问题就变成了两数之和
nums.sort() # 排序
dict = {}
for i in range(0, n-1): # 固定第一个数
# 只有和上一次的数字不同时,才会进行枚举
if i == 0 or nums[i] != nums[i-1]:
for j in range(i+1, n-2): # 固定第二个数
# 只有和上一次的数字不同时,才会进行枚举
if j == i+1 or nums[j] != nums[j-1]:
left = j + 1
right = len(nums) - 1
ans = 0
while left < right:
ans = nums[i] + nums[j] + nums[left] + nums[right]
if ans < target:
left += 1
elif ans > target:
right -= 1
else:
dict[str([nums[i], nums[j], nums[left], nums[right]])] = [nums[i], nums[j], nums[left], nums[right]]
left += 1
right -= 1
return list(dict.values())
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
方法:分组+哈希表
- 我们可以将四个数组分成两部分,
nums1和nums2为一组,nums3和nums4为另外一组。对于nums1和nums2,我们使用二重循环对它们进行遍历,得到所有nums1[i] + nums2[j]的值并存入哈希映射中。对于哈希映射中的每个键值对,每个键表示一种nums1[i] + nums2[j],对应的值为nums1[i] + nums2[j]出现的次数。 - 对于
nums3和nums4,我们同样使用二重循环对它们进行遍历。当遍历到nums3[k]+nums4[l]时,如果nums3[k]+nums4[l]的负值出现在哈希映射中,则将nums3[k]+nums4[l]对应的值累加到结果中。
from collections import defaultdict
class Solution:
def fourSumCount(self, nums1: List[int], nums2: List[int], nums3: List[int], nums4: List[int]) -> int:
map = defaultdict(int) # 和的哈希映射
n = len(nums1)
ans = 0
sum_1 = 0
for i in range(n):
for j in range(n):
sum_1 = nums1[i] + nums2[j]
map[sum_1] += 1
sum_2 = 0
for k in range(n):
for l in range(n):
sum_2 = -(nums3[k] + nums4[l])
if sum_2 in map:
ans += map[sum_2]
return ans
1334

被折叠的 条评论
为什么被折叠?



