目录
文档讲解:代码随想录 哈希表理论基础、代码随想录 有效的字母异位词、代码随想录 两个数组的交集、代码随想录 快乐数、代码随想录 两数之和
视频讲解:双指针法经典题目 | LeetCode:977.有序数组的平方、学透哈希表,数组使用有技巧!Leetcode:242.有效的字母异位词、学透哈希表,set使用有技巧!Leetcode:349. 两个数组的交集、梦开始的地方,Leetcode:1.两数之和,学透哈希表,map使用有技巧!
哈希表理论基础
哈希表
哈希表也叫散列表(Hash table),根据关键码的值而直接进行访问的数据结构。哈希表可以用来快速判断一个元素是否是现在集合里。其实数组就是一张哈希表,哈希表中关键码就是数组的索引下标,然后通过下标直接访问数组中的元素。
举例,要查询一个名字是否在这所学校里,用哈希表查找的方式
O
(
1
)
O(1)
O(1)就可以做到。
哈希函数
哈希函数,可以将学生的姓名直接映射为哈希表上的索引,然后通过查询索引下标快速知道这位同学是否在这所学校里。
哈希函数,通过hashCode把名字转化为数值,一般hashcode通过特定的编码方式,将其他数据格式转化为不同数值,这样就把学生名字映射为哈希表上的索引数字了。
如果hashcode得到的数值大于哈希表大小了,为了保证映射出来的索引值都落在哈希表上,我们对数值做一个取模操作。
常见的哈希函数
- 除法哈希法: h ( k ) = k % m h(k) = k \% m h(k)=k%m
- 乘法哈希法: h ( k ) = f l o o r ( m ∗ ( A ∗ k e y % 1 ) ) h(k) = \rm{floor}(m*(A*key\%1)) h(k)=floor(m∗(A∗key%1))
- 全域哈希法: h a , b ( k ) = ( ( a ∗ k e y + b ) m o d p ) m o d m a , b = 1 , 2 , . . . , p − 1 h_{a,b}(k)=((a*key + b) \rm{mod} \, p) mod\, m \quad a, b = 1, 2, ..., p-1 ha,b(k)=((a∗key+b)modp)modma,b=1,2,...,p−1
哈希碰撞
如果学生数量大于哈希表大小,无法避免有几位同学映射到哈希表同一个索引下标,这种现象叫哈希碰撞。一般有两种解决方法,拉链法和线性探测法。
拉链法
发生冲突的元素储存在一个链表中。本质上拉链法就是要选择适当的哈希表大小,这样既不会因为数组空值而浪费大量内存,也不会因为链表太长而在查找上浪费太多时间。
线性探测法
使用线性探测法,一定要保证tablesize > datasize。对于冲突的位置,向下寻找一个空位放另一个数值,所以如果不满足tablesize > datasize,哈希表上就没有空位置存放冲突的数据了。
二次探测法
如果位置 i i i被占用,则探查 i + 1 2 i+1^2 i+12, i − 1 2 i-1^2 i−12, i + 2 2 i+2^2 i+22, i − 2 2 i-2^2 i−22 …
二度哈希
有 n n n个哈希函数,当使用第1个哈希函数h1发生冲突时,则尝试使用h2, h3 …
常见的三种哈希结构
- 数组
- set (集合)
- map (映射)
在python中,字典与集合都是通过哈希表来实现的。
LeetCode 242.有效的字母异位词
力扣题目链接
题目描述:给定两个字符串 s
和 t
,编写一个函数来判断 t
是否是 s
的字母异位词。
注意:若 s
和 t
中每个字符出现的次数都相同,则称 s
和 t
互为字母异位词。
示例1:
输入: s = "anagram", t = "nagaram"
输出: true
示例 2:
输入: s = "rat", t = "car"
输出: false
提示:
1 <= s.length, t.length <= 5 * 10^4
s 和 t 仅包含小写字母
解题思路
- 本题可以遍历字符串
s
中每个字母出现的次数统计为哈希表,然后遍历t
中的每个字母出现则将哈希表中的次数减1,若最终哈希表中所有元素都为0,则互为字母异位词,否则就不是。 - 由于本题涉及字母只有26个,所以可以用数组的方式构建哈希表,python中
ord("a")
可以获得对应字母的ASCII码。
代码如下:
# 解法一:数组哈希表
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 record:
if i != 0:
return False
return True
# 解法二:Counter字典哈希表
from collections import Counter
class Solution:
def isAnagram(self, s: str, t: str) -> bool:
s_cnt = Counter(s)
t_cnt = Counter(t)
return s_cnt == t_cnt
相关题目
LeetCode 383. 赎金信
力扣题目链接
本题还是很简单,使用数组哈希表即可,代码如下:
class Solution:
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
need = [0] * 26
zeros = [0] * 26
for i in ransomNote:
need[ord(i) - ord('a')] += 1
for i in magazine:
if need[ord(i) - ord('a')] > 0:
need[ord(i) - ord('a')] -= 1
if need == zeros:
return True
return False
LeetCode 49. 字母异位词分组
力扣题目链接
由于本题中的单词只包含小写字母,所以最简单的方式是用数组哈希表表示每个单词,这样字母异位词对应的数组是完全相同的,代码如下:
from collections import defaultdict
class Solution:
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
mp = defaultdict(list)
for string in strs:
counts = self.convert(string)
mp[tuple(counts)].append(string)
return list(mp.values())
def convert(self, string):
record = [0] * 26
for i in string:
record[ord(i) - ord('a')] += 1
return record
LeetCode 438. 找到字符串中所有字母异位词
简单用数组哈希会超时:
class Solution:
def findAnagrams(self, s: str, p: str) -> List[int]:
n = len(p)
target = self.convert(p)
result = []
if len(s) < n:
return result
for i in range(len(s) - n + 1):
if self.convert(s[i:i+n]) == target:
result.append(i)
return result
def convert(self, string):
record = [0] * 26
for i in string:
record[ord(i) - ord('a')] += 1
return record
滑动窗口 + 数组哈希:
class Solution:
def findAnagrams(self, s: str, p: str) -> List[int]:
p_len = len(p)
s_len = len(s)
result = []
if s_len < p_len:
return result
p_count = [0] * 26
s_count = [0] * 26
for i in range(p_len):
p_count[ord(p[i]) - ord('a')] += 1
s_count[ord(s[i]) - ord('a')] += 1
if s_count == p_count: result.append(0)
for i in range(s_len - p_len):
s_count[ord(s[i]) - ord('a')] -= 1
s_count[ord(s[i+p_len]) - ord('a')] += 1
if s_count == p_count:
result.append(i+1)
return result
LeetCode 349. 两个数组的交集
力扣题目链接
题目描述:给定两个数组 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
解题思路
本题还是较为简单的,注意的是如果没有提示中1 <= nums1.length, nums2.length <= 1000
的限制,本题是不适合用数组的哈希方式的,因为如果数据比较分散会浪费很多空间,本题最合适的哈希方式是set。
代码如下:
# 集合
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
return list(set(nums1) & set(nums2))
# 字典
from collections import Counter
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
result = []
cnt = Counter(nums1)
for num in nums2:
if cnt[num] > 0 and num not in result:
result.append(num)
return result
相关题目
LeetCode 350. 两个数组的交集 II
题目描述:给你两个整数数组 nums1
和 nums2
,请你以数组形式返回两数组的交集。返回结果中每个元素出现的次数,应与元素在两个数组中都出现的次数一致(如果出现次数不一致,则考虑取较小值)。可以不考虑输出结果的顺序。
示例 1:
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2,2]
示例 2:
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[4,9]
解题思路:本题可以用一个字典记录出现元素的次数,遍历另一个数组时每当遇到一个出现过的元素,将次数减一,并记录这个数即可。
代码如下:
from collections import Counter
class Solution:
def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:
result = []
cnt = Counter(nums1)
for num in nums2:
if cnt[num] > 0:
result.append(num)
cnt[num] -= 1
return result
LeetCode 202. 快乐数
力扣题目链接
题目描述:
编写一个算法来判断一个数 n
是不是快乐数。
「快乐数」 定义为:
- 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
- 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
- 如果这个过程 结果为 1,那么这个数就是快乐数。
如果n
是 快乐数 就返回true
;不是,则返回false
。
解题思路
本题有三种可能:
- 最终会得到1
- 最终会进入循环
- 值会越来越大,最后接近无穷大
然而第三种是否存在呢?
Digits | Largest | Next |
---|---|---|
1 | 9 | 81 |
2 | 99 | 162 |
3 | 999 | 243 |
4 | 9999 | 324 |
13 | 9999999999999 | 1053 |
通过这个表可以看出,三位数的位平方和不可能大于243,这也是一个三位数,那么它的位平方和就会更小,直到进入循环或者跌到1。对于位数比较大的数来说,即使是13位数,它的位平方和也不会超过1053,再次计算又会跌入三位数。由此可见上述的第三种情况是不会出现的。
求解这道题只需要记录每次的位平方和,如果得到1就直接返回True,如果出现重复数字了,说明陷入循环了,返回False。
代码如下:
class Solution:
def isHappy(self, n: int) -> bool:
record = set()
while True:
n = self.bitSquareSum(n)
if n == 1:
return True
if n in record:
return False
record.add(n)
def bitSquareSum(self, num):
result = 0
res = num
while res != 0:
result += (res % 10)**2
res = res // 10
return result
LeetCode 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 <= 10^4
-10^9 <= nums[i] <= 10^9
-10^9 <= target <= 10^9
解题思路
本题使用map这种哈希结构(python中的字典),对于每一次遍历到的数字,我们在哈希表中key存放数值,value存放下标,同时在哈希表中查找 target - num
,如果找到直接返回二者的下标即可。
代码如下:
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
hash_table = {}
for i, num in enumerate(nums):
if target - num in hash_table.keys():
return [hash_table[target - num], i]
else:
hash_table[num] = i
今日毕,加油!