Day 05
开始学习哈希表。用C++会比较麻烦,目前用python,感觉很多坑都跳过去了
哈希表
知识点
哈希表是根据关键码的值而直接进行访问的数据结构。
简单来说,数组也属于哈希表,哈希表中关键码就是数组的索引下标,然后通过下标直接访问数组中的元素。
哈希表一般是用来快速判断一个元素是否出现集合里。
将集合的所有内容都通过某种映射存入哈希表,通过索引就能知道该元素是否包含在集合中。这种映射就被称为哈希函数。
哈希函数如下图所示,通过hashCode把元素转化为数值,一般hashcode是通过特定编码方式,将其他数据格式转化为不同的数值存入哈希表。
如果hashCode得到的数值大于哈希表的大小tableSize,为了保证索引数值落在哈希表上,会在再次对数值做一个取模的操作。如果元素的数量大于哈希表的大小,无法避免几个元素同时映射到哈希表的同一个索引下标的位置,即为哈希碰撞。
哈希碰撞:一般有两种解决方式——拉链法和线性探测法。
拉链法:发生冲突的元素都被存储在链表同一单元中。选择适当的哈希表的大小,这样既不会因为数组空值而浪费大量内存,也不会因为链表太长而在查找上浪费太多时间。(数据规模是dataSize, 哈希表的大小为tableSize)
线性探测法:要求tableSize大于dataSize。如果产生冲突的位置放了小李,那么就向下找一个空位放置小王的信息。
常见的哈希结构:数组,集合(set),映射(map)
(C++的内容,后面再看)
std::unordered_set底层实现为哈希表,std::set 和std::multiset 的底层实现是红黑树,红黑树是一种平衡二叉搜索树,所以key值是有序的,但key不可以修改,改动key值会导致整棵树的错乱,所以只能删除和增加。
std::unordered_map 底层实现为哈希表,std::map 和std::multimap 的底层实现是红黑树。同理,std::map 和std::multimap 的key也是有序的(这个问题也经常作为面试题,考察对语言容器底层的理解)。
当我们要使用集合来解决哈希问题的时候,优先使用unordered_set,因为它的查询和增删效率是最优的,如果需要集合是有序的,那么就用set,如果要求不仅有序还要有重复数据的话,那么就用multiset。
map 是一个key value 的数据结构,map对key是有限制,对value没有限制的,因为key的存储方式使用红黑树实现的。
总结
当我们遇到要快速判断一个元素是否出现集合里的时候,就要考虑哈希法。但是哈希法也是牺牲了空间换取了时间,因为我们要使用额外的数组,set或者是map来存放数据,才能实现快速的查找。如果在做面试题目的时候遇到需要判断一个元素是否出现过的场景也应该第一时间想到哈希法!
数组
242. 有效的字母异位词
给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。注意:若 s 和 t 中每个字符出现的次数都相同,则称 s 和 t 互为字母异位词。
解:一般限制了数值的大小的题目用数组。定义一个长度为26的数组record来记录字符串中对应字母出现的频率,s中出现一个对应位置+1,t中出现一个-1。
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
相关题目
383.赎金信(opens new window)
49.字母异位词分组
438.找到字符串中所有字母异位词
集合
349. 两个数组的交集
给定两个数组 nums1 和 nums2 ,返回它们的交集。输出结果中的每个元素一定是唯一的。可以不考虑输出结果的顺序。
解:python直接一个set就能解决……复杂一点就用字典。
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
dicts = {}
result = []
for num in nums1:
dicts[num] = 1
for num in nums2:
if num in dicts.keys() and dicts[num] == 1:
dicts[num] = 0
result.append(num)
return result
相关题目
350.两个数组的交集 II
202. 快乐数
编写一个算法来判断一个数 n 是不是快乐数。「快乐数」定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。然后重复这个过程直到这个数变为 1,也可能是无限循环但始终变不到 1。如果这个过程 结果为 1,那么这个数就是快乐数。如果 n 是 快乐数 就返回 true ;不是,则返回 false 。
解:题目提到了无限循环,用哈希表查找计算结果是否曾经出现过即可
class Solution:
def get_sum(self, n):
sum = 0
while n:
sum += (n-int(n/10)*10)**2
n = int(n/10)
return sum
def isHappy(self, n: int) -> bool:
record = set() // set()不能用set(n)定义,括号中必须放数组、字符串等
while 1:
n = self.get_sum(n)
if n == 1:
return True
elif n in record:
return False
else:
record.add(n)
映射
1. 两数之和
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出和为目标值 target 的那两个整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。可以按任意顺序返回答案。
解:不仅要知道元素有没有遍历过,还要知道这个元素对应的下标,需要使用key-value结构来存放,key来存元素,value来存下标,那么使用map正合适。数组和集合来做哈希法的局限为:数组的大小受限,如果元素很少,哈希值太大会造成内存空间的浪费;set是一个集合,里面放的元素只能是一个key,而两数之和这道题目,不仅要判断y是否存在而且还要记录y的下标位置,因为要返回x和y的下标。
map作为一种key-value结构,存储结构为 {key:数据元素,value:数组元素对应的下标}。
python可以用字典,也是一种映射。
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
record = {}
result = []
i = 0
for num in nums:
if target-num in record.keys():
result = [record[target-num],i]
else:
record[num] = i
i += 1
return result