哈希表基础
什么时候用哈希表
哈希表能够快速判断某个元素是否存在在一个集合中,不用像数组/链表一样遍历。
理解哈希表,哈希函数,哈希冲突
将学生姓名映射到哈希表中,需要使用哈希函数。哈希函数会按照某种固定的计算规则,把学生姓名转换成一个整数,再进一步映射为哈希表中的下标。
这和直接给每个学生分配一个学号不太一样。学号通常是人为或系统分配的唯一编号,学生和学号之间的对应关系需要提前保存,学号本身一般不能通过学生姓名直接计算得到。
而哈希值不需要提前分配,只要给定同一个姓名和同一个哈希函数,就可以按照固定规则再次计算出相同的哈希值,并据此定位到哈希表中的位置。
不过,不同姓名可能得到相同的哈希位置,这叫哈希冲突,所以哈希并不是严格的一一对应关系。
具体如何学生姓名转换成哈希表下表以及如何解决哈希冲突的常见方法,可以看代码随想录
常见的三种哈希结构(python)
| 数据结构 | Python | 核心用途 | 查找方式 |
|---|---|---|---|
| 数组 | list | 按位置存数据 | 下标 |
| 集合 | set | 判断元素是否存在、去重 | 哈希 |
| 映射 | dict | key → value | 哈希 |
了解字符编码
最早的字符编码就是ASCII编码,编码了128个字符(英文,数字,符号)。
之后出现了Unicode编码,目标就是编码世界上所有的字符。unicode编完码存储在计算机里的方式叫做(utf-8,utf-16)...。Unicode 编码是兼容ASCII编码的(前128个字符完全保留了ASCII编码)
汉字编码还有GBK格式
有效的字母异位词
class Solution:
def isAnagram(self, s: str, t: str) -> bool:
record=[0]*26
for word_s in s:
record[ord(word_s)-ord('a')]+=1
for word_t in t:
record[ord(word_t)-ord('a')]-=1
for i in record:
if i!=0:
return False
return True
思路不是很难:统计字符串里每个字符的出现次数,如果两个字符串里,所有字符出现的次数相同,那么这两个字符串就是字母异位词。想想怎么对应到哈希表的概念里。
首先构造了一个 table_size=26的哈希表(数组),因为题目内容只涉及小写英文字母,所以26个就够了
然后把字符串的每个字母,通过哈希函数 ord(word)-ord('a') 映射到哈希表里的唯一下标 0~25。
这道题哈希函数比较简单,而且不涉及哈希冲突
两个数组的交集
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
set_num1=set(nums1)
set_num2=set(nums2)
result=[]
for i in set_num1:
for j in set_num2:
if(i==j):
result.append(i)
return result
这个方法正确,但是时间复杂度大致是o(mn),可以利用python set底层是用哈希表实现的这一条(python set 底层其实实现起来还是有点复杂的,这里先不探究底层原理),来简化时间复杂度
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
set_num1=set(nums1)
set_num2=set(nums2)
result=[]
for i in set_num1:
if i in set_num2: #相当于利用哈希表直接找到元素候选位置,不用一个一个遍历,就节省了时间复杂度
result.append(i)
return result
利用集合去重的性质,然后双重 for 循环求解
我还有个思路就是首先利用集合去重,然后做哈希映射,数字0对应下标0,数字1对应下标1。然后会得到set_num1,set_num2的两串向量vector1,vector2,然后按位取交集,最后为1的位置对应的元素就是两个数组的交集。但这个方法的问题就是 vector 会非常的长,空间复杂度很高
chagpt夸了我一顿,并把我的想法做了一个梳理:
将数字本身映射为数组下标,用 0/1 表示该数字是否出现,再对两个数组按位求交集。这种方法本质上属于直接寻址或 Bitmap 思想,不存在普通哈希表中的哈希冲突,但空间复杂度取决于数字的取值范围;当数值范围很大而数据稀疏时,会造成大量空间浪费。
最后还有一个最简版本,集合交集运算
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
return list(set(nums1) & set(nums2))
快乐数
数学原理(简单了解一下)
定义一个规则:f(n)=n每个位置上的平方和,然后执行
while True:
cur_sum=f(n)
n=cur_sum
这样一直计算下去,cur_sum一定会重复,原因如下:
- f(n)这个规则一定会把数字压缩到一个有限的范围内。定义一个有d位的数字,那么平方和的最大数就是81d。比如一个100位的数字,其平方和最大就是8100,被极大的压缩了。所以f(n)这个规则会把一个数压缩到有限范围内。
- 抽屉原理:101个苹果,100个抽屉,至少有一个抽屉会放两个苹果->比如一共100个数字,你抽101次,至少有一个数字会重复
而且 cur_sum 重复之后一定会进入循环:因为每个数字的下一步(计算f(n)的结果)一定是固定的,可以理解为每个房间只有一个出口
python代码
class Solution:
def square_sum(self,n):
num=[]
result=0
while(n//10!=0):
num.append(n%10)
n=n//10
num.append(n%10)
for i in num:
result+=i**2
return result
def isHappy(self, n: int) -> bool:
sum_set=set()
while True:
square_sum=self.square_sum(n)
if square_sum==1:
return True
elif square_sum in sum_set:
return False
sum_set.add(square_sum)
n=square_sum
主要就是两部分:
- 各个位置平方和
- 判断是否为快乐数(要么平方和一直缩小到1,要么出现重复并开始循环)