哈希表Hash Table理论基础:
当遇到要快速判断一个元素是否出现在集合时,就要考虑哈希法。(如果枚举法,时间复杂度O(n),但哈希表O(1)。哈希表,牺牲了空间,换取时间)
【哈希表】:能够直接通过索引下标,直接访问数据。
【哈希表内部实现原理】:把所有内容都存进哈希表,查询的时候,通过索引直接就可以知道在不在表里面。
【哈希函数】:把 内容 映射成—> 哈希表上的索引。
【哈希碰撞】:
出现的原因:哈希表装不下所有的内容。可能出现几个内容,映射到一个索引。
解决方法:
-
拉链法:其实拉链法就是要选择适当的哈希表的大小,这样既不会因为数组空值而浪费大量内存,也不会因为链表太长而在查找上浪费太多时间。
-
线性探测法:使用线性探测法,一定要保证tableSize大于dataSize。 我们需要依靠哈希表中的空位来解决碰撞问题。
【常见哈希表的区别】:
红黑树是一种平衡二叉搜索树,所以key值是有序的,但key不可以修改,改动key值会导致整棵树的错乱,所以只能删除和增加。
底层实现使用的哈希表,key值是无序的。
【数组、set和map】:
- 当我们要使用集合来解决哈希问题的时候,优先使用unordered_set,因为它的查询和增删效率是最优的,如果需要集合是有序的,那么就用set,如果要求不仅有序还要有重复数据的话,那么就用multiset。
- 在map 是一个key value 的数据结构,map中,对key是有限制,对value没有限制的,因为key的存储方式使用红黑树实现的。
242.有效的字母异位词
【状态】:前一天晚上把代码都过了一遍,所以写出来了。但是犯了个小错误,
- while里面变量要手动去调整+1,但是for不用,for自动迭代,自动抛出抛出下一个值。
- 掌握相对位置, ord() 给出字符的ascll编码
【题目】:
【思路】:
【解法1】:时间复杂度O(n),空间复杂度O(1):数组是一个常量的辅助数组
- 定义一个大小26的数组record,初始化为0(因为字符a到字符z的ASCII也是26个连续的数值,为什么能用数组,因为这里显然使用的是个有限数组,否则不能使用)
- 先去遍历字符串s(用record记录字符出现的次数,字符对应索引的数值+1),然后在遍历字符串t(字符对应索引的数值-1)
3.最后检查record数组,是否所有元素为0,是s和t是字母异位词,则true;否则,则两个字母长得不一样,return fase
注意:并不需要记住字符a的ASCII,只要求出一个相对数值就可以了。
class Solution:
def isAnagram(self, s: str, t: str) -> bool:
record = [0] * 26
for i in s:
#并不需要记住字符a的ASCII,只要求出一个相对数值就可以了
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:
#record数组如果有的元素不为零0,说明字符串s和t 一定是谁多了字符或者谁少了字符。
return False
return True
【解法2】:使用特殊的函数
collections.defaultdict 是 python 标准库中的一个字典子类。与普通字典不同的是,当访问一个不存在的键时,它会自动创建一个默认值。并对key自动排序。
这里的 defaultdict(int) 表示当访问不存在的键时,会自动创建一个值为 0 的键。
class Solution:
def isAnagram(self, s: str, t: str) -> bool:
from collections import defaultdict
s_dict = defaultdict(int)
t_dict = defaultdict(int)
for x in s:
s_dict[x] += 1
for x in t:
t_dict[x] += 1
return s_dict == t_dict
collections.Counter是一个计数器,用于跟踪元素出现的次数。count里面自动对key排序
class Solution(object):
def isAnagram(self, s: str, t: str) -> bool:
from collections import Counter
a_count = Counter(s)
b_count = Counter(t)
return a_count == b_count
349. 两个数组的交集:使用set()
【状态】:刚开始只想到用上面那题数组的方法解。但是说尽可能用set去解题,没想出来,看了代码以后才会写。但是自己写的时候,犯了个小错误。
- 我想要查找键在不在字典里面,不能使用 if table[j] != 0(应该有些字符没有出现在字典里面,所以找不到value),只能使用if j in table,查找键在不在链表里面。
- table[i] = table.get(i,0) + 1,如果键在,就给value+1;如果不在,先赋值为0,在+1。
【题目】:
【思路】:根据题目描述,结果要用到无序set()就行;
【解法一】字典+数组
- 先用遍历nums1,用字典存储所有元素, key是元素,value是字符出现的次数
- 再遍历nums2,看看字符能不能在字典找到,找到把字符加到res集合里面,然后在字典里面删除这个字符
- 返回list(res)
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
# 使用哈希表存储一个数组中的所有元素
table = {}
for num in nums1:
table[num] = table.get(num, 0) + 1
# 使用集合存储结果
res = set()
for num in nums2:
if num in table:
res.add(num)
del table[num]
return list(res)
【解法二】使用集合set()的特殊方法
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
return list(set(nums1) & set(nums2))
#&操作符是取交集;and关键字是两边为正,才是真
【解法三】使用数组
跟上一题一样,建一个数组,遍历num1和nums2,对数组进行判断。
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
count1 = [0]*1001
count2 = [0]*1001
result = []
for i in range(len(nums1)):
count1[nums1[i]]+=1
for j in range(len(nums2)):
count2[nums2[j]]+=1
for k in range(1001):
if count1[k]*count2[k]>0:
result.append(k)
return result
202. 快乐数: 使用set() [这道题转成数组也行]
【状态】: ==n, r = divmod(n, 10) == #就是n除以10,n是商,t是余数
数组.append(元素) 和 集合.add(元素)
总而言之,这道题,我自己写的时候,尽管知道了解题思路,在逻辑上,写的时候屡不清楚!!
在这个循环里面,快指针去追赶慢指针,只要fast(slow)指针一旦值为1,里面返回true,一旦追赶上,说明是一个死循环,并且在这个循环里面不可能有1,返回false。
我一直写错的原因就是,一旦判断可能循环次数不够,导致还没出现1,就被否定成不是快乐数了。
【题目】:
【思路】:
1.快速判断一个元是否在集合中,要用哈希法----->考虑使用set,dict,nums数组
2.注意 == 求和的过程==
【解法1】使用集合set()
class Solution:
def isHappy(self, n: int) -> bool:
record = set()
while True:
n = self.get_sum(n)
if n == 1:
return True
# 如果中间结果重复出现,说明陷入死循环了,该数不是快乐数
if n in record:
return False
else:
record.add(n)
def get_sum(self,n: int) -> int:
new_num = 0
while n:
n, r = divmod(n, 10)
new_num += r ** 2
return new_num
【解法二】逻辑不同,取数平方和,使用了转换字符串遍历字符,和字符转int算平方。【这个更重要,把握循环条件!!】
class Solution:
def isHappy(self, n: int) -> bool:
record = set()
while n not in record:
record.add(n)
new_num = 0
n_str = str(n)
for i in n_str:
new_num+=int(i)**2
if new_num==1: return True
else: n = new_num
return False
【解法三】竟然还能用快慢指针:因为这道题就是个无限循环的过程,这个过程如果拦截到n为1,返回true;是死循环就是false
在这个循环里面,快指针去追赶慢指针,只要fast(slow)指针一旦值为1,里面返回true,一旦追赶上,说明是一个死循环,并且在这个循环里面不可能有1,返回false。否则写出来的逻辑就是,一旦判断可能循环次数不够,导致还没出现1,就被否定成不是快乐数了。
class Solution:
def isHappy(self, n: int) -> bool:
slow = n
fast = n
while self.get_sum(fast) != 1 and self.get_sum(self.get_sum(fast)):
slow = self.get_sum(slow)
fast = self.get_sum(self.get_sum(fast))
if slow == fast:
return False
return True
def get_sum(self,n: int) -> int:
new_num = 0
while n:
n, r = divmod(n, 10)
new_num += r ** 2
return new_num
【解法四】集合+精简
class Solution:
def isHappy(self, n: int) -> bool:
seen = set()
while n != 1:
n = sum(int(i) ** 2 for i in str(n))
if n in seen:
return False
seen.add(n)
return True
1.两数之和
【状态】:就是比如说,我现在有了一个数,根据目标,我判断出我还需要一个数,我需要知道这个数在不在list里面,用哈希可以,但是索引怎么获取??
我们不仅要知道元素有没有遍历过,还要知道这个元素对应的下标,需要使用 key value结构来存放,key来存元素,value来存下标,那么使用map正合适。所以 map中的存储结构为 {key:数据元素,value:数组元素对应的下标}。
【题目】
【思路】
再来看一下使用数组和set来做哈希法的局限。
- 数组的大小是受限制的,而且如果元素很少,而哈希值太大会造成内存空间的浪费。
- set是一个集合,里面放的元素只能是一个key,而两数之和这道题目,不仅要判断y是否存在而且还要记录y的下标位置,因为要返回x 和 y的下标。所以set 也不能用。
- map是一种key value的存储结构,可以用key保存数值,用value在保存数值所在的下标。
时间复杂度: O(n)、空间复杂度: O(n)
【解法一】使用字典
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
records = dict()
for index, value in enumerate(nums):
if target - value in records: # 遍历当前元素,并在map中寻找是否有匹配的key
return [records[target- value], index]
records[value] = index # 遍历当前元素,并在map中寻找是否有匹配的key
return []
【解法二】使用集合:使用 == 数组.index(元素) == 返回元素在数组的索引
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
#创建一个集合来存储我们目前看到的数字
seen = set()
for i, num in enumerate(nums):
complement = target - num
if complement in seen:
return [nums.index(complement), i]
seen.add(num)
【解法三】双指针:先排序,left指针指向首,right指针指向尾巴;如果两个指针指向数组的和达到target,返回下标(如果两个指针再原list索引相同,比如两个一样的数,给出的index一样,就要对所有做一些特殊处理);否则,如果小,就left+1,如果大了,就right-1。
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
# 对输入列表进行排序
nums_sorted = sorted(nums)
# 使用双指针
left = 0
right = len(nums_sorted) - 1
while left < right:
current_sum = nums_sorted[left] + nums_sorted[right]
if current_sum == target:
# 如果和等于目标数,则返回两个数的下标
left_index = nums.index(nums_sorted[left])
right_index = nums.index(nums_sorted[right])
if left_index == right_index:
right_index = nums[left_index+1:].index(nums_sorted[right]) + left_index + 1
return [left_index, right_index]
elif current_sum < target:
# 如果总和小于目标,则将左侧指针向右移动
left += 1
else:
# 如果总和大于目标值,则将右指针向左移动
right -= 1
【解法四】暴力法时间复杂度O(n2)
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
for i in range(len(nums)):
for j in range(i+1, len(nums)):
if nums[i] + nums[j] == target:
return [i,j]
【总结】
情况 | 哈希表 |
---|---|
空间有限 | 优先考虑用构建一个 数组 |
空间无限,存一个信息 | 考虑set() |
空间无限、需要分别存两个值 | dict |
目前做题遇到的两个指针的解法:
双指针 |
---|
快慢指针(一倍数和二倍速),特别在循环题目 |
首尾指针 |
首首指针,出发点不同 |
首首指针,一个先跑n步,在一起跑 |