学习笔记@代码随想录day5:哈希表part01
哈希表理论基础
哈希表是根据关键码的值而直接进行访问的数据结构。
简单理解,数组就是一张哈希表:哈希表中关键码就是数组的索引下标,然后通过下标直接访问数组中的元素。
用来干啥?一般哈希表都是用来快速判断一个元素是否出现集合里。
例如要查询一个名字是否在这所学校里。
要枚举的话时间复杂度是O(n),但如果使用哈希表的话, 只需要O(1)就可以做到。
通过哈希函数直接将学生的名字映射为哈希表上的索引,然后通过查询索引下标快速知道某位同学是否在这个学校里。其中hashCode可以把名字转为数值。为了保证得到的数值都落在哈希表上,对数值做取模操作。
如果学生的数据大于哈希表,会产生哈希碰撞问题。
哈希碰撞
上图中小李和小王同时映射到一个地址上,即哈希碰撞现象。一般哈希碰撞有两种解决方法, 拉链法和线性探测法。
拉链法
线性探测法
使用线性探测法,一定要保证tableSize大于dataSize。 我们需要依靠哈希表中的空位来解决碰撞问题。
常见的三种数据结构有数组、set(集合)、map(映射)。
集合(Set)- 在Python中,集合可以使用set来表示。集合是一种无序的数据结构,可以存储不重复的元素。可以使用add()方法向集合中添加元素,可以使用remove()方法移除集合中的元素。
fruits = {"apple", "banana", "orange"}
fruits.add("grape")
fruits.remove("banana")
print(fruits) # 输出{'orange', 'grape', 'apple'}
映射(Map)- 在Python中,映射可以使用字典(Dictionary)来表示。字典是一种无序的键值对数据结构,可以存储多个元素,每个元素由一个键和一个值组成。可以使用键来访问字典中的值。
person = {"name": "Tom", "age": 20, "gender": "male"}
print(person["name"]) # 输出Tom
总结:当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法。但是哈希法也是牺牲了空间换取了时间,因为我们要使用额外的数组,set或者是map来存放数据,才能实现快速的查找。
有效的字母异位词 (力扣242)
下边是自己学习前写的暴力代码,测试时存在问题,对于类似于输入为"aacc","ccac"是错误的,因为我只判断数组里是否存在某个字符,判断完成后并没有去掉这个字符,还可以作为下次判断的条件,所以出错。
class Solution:
def isAnagram(self, s: str, t: str) -> bool:
s_time=0
t_time=0
for i in s:
if i in t:
s_time+=1
else:
return False
for i in t:
if i in s:
t_time+=1
else:
return False
if s_time==t_time:
return True
else:
return False
没有找到合适的移除元素的方法,不做深度研究了。
哈希表方法
1.定义一个数组来记录字符串s出现次数。
2.需要把字符映射到数组也就是哈希表的索引下标上,因为字符a到字符z的ASCII是26个连续的数值,所以字符a映射为下标0,相应的字符z映射为下标25。
3。再遍历 字符串s的时候,只需要将 s[i] - ‘a’ 所在的元素做+1 操作即可,并不需要记住字符a的ASCII,只要求出一个相对数值就可以了。 这样就将字符串s中字符出现的次数,统计出来了。
4.那看一下如何检查字符串t中是否出现了这些字符,同样在遍历字符串t的时候,对t中出现的字符映射哈希表索引上的数值再做-1的操作。
5.那么最后检查一下,record数组如果有的元素不为零0,说明字符串s和t一定是谁多了字符或者谁少了字符,return false。
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
代码中的ord(i) - ord(“a”)用于计算当前字符相对于小写字母"a"的偏移量,即"a"对应的偏移量为0,"b"对应的偏移量为1,"c"对应的偏移量为2,以此类推。
两个数组的交集 (力扣349)
#题目描述
给定两个数组 nums1 和 nums2 ,返回 它们的交集 。
输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。
用数组自己写了个暴力的解法。
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
same_list=[]
for i in nums1:
if i in nums2:
if i not in same_list:
same_list.append(i)
return same_list
下面是set解决此问题:
# 将两个数组转换为set类型
set1, set2 = set(nums1), set(nums2)
# 求两个set的交集
#在Python中,使用&运算符可以对集合进行按位与(Bitwise AND)运算,
#得到两个集合中共同存在的元素。
intersection = set1 & set2
# 将交集转换为列表类型并返回
return list(intersection)
#或使用以下方法
#set1, set2 = set(nums1), set(nums2)
#在Python中,set类提供了intersection()方法来返回两个集合的交集。
#intersection = set1.intersection(set2)
#return list(intersection)
随想录上的代码,自己手敲理解了下。
'''使用字典和集合'''
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
# 使用字典存储nums1中的元素及其出现次数
table = {}
for num in nums1:
#如果num在字典中已经存在,将其对应的值加1;
#否则,将其作为新的key插入字典,并将对应的值初始化为1。
table[num] = table.get(num, 0) + 1
# 使用集合存储结果
res = set()
for num in nums2:
# 如果num在字典中出现过,则将其加入结果集合,并从字典中删除
if num in table:
res.add(num)
del table[num]
# 将集合转换为列表并返回
return list(res)
快乐数 (202)
'''
编写一个算法来判断一个数 n 是不是快乐数。
「快乐数」 定义为:
对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
如果这个过程 结果为 1,那么这个数就是快乐数。
输入:n = 19
输出:true
解释:
1^2 + 9^2 = 82
8^2 + 2^2 = 68
6^2 + 8^2 = 100
1^2 + 0^2 + 0^2 = 1
'''
当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法了。
'''
使用集合
'''
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)
#get_sum()方法使用while循环遍历整数n的每个位,将各个位上数字的平方累加到一个新的变量new_num中,并返回new_num。
def get_sum(self,n:int)->int:
new_num=0
while n:
#divmod()函数的用法是:divmod(a, b),其中a和b都是数字,表示对a进行b的除法运算
#返回一个元组,第一个元素为a除以b的商,第二个元素为a除以b的余数。
n,r=divmod(n,10)
new_num+=r**2
return new_num
'''使用集合'''
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)
#将整数n转换为字符串n_str是为了方便遍历n的各个位上的数字。
for i in n_str:
new_num+=int(i)**2
if new_num==1:
return True
else:
n = new_num
return False
两数之和(1)
'''
给定一个整数数组 nums 和一个整数目标值 target
请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
'''
'''暴力'''
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]
'''使用字典'''
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
#定义一个字典
records = dict()
#在遍历列表时,可以使用enumerate()函数同时获取列表元素的下标和值
for index, value in enumerate(nums):
if target - value in records: # 遍历当前元素,并在map中寻找是否有匹配的key
return [records[target- value], index]
records[value] = index # 遍历当前元素,并在map中寻找是否有匹配的key
return []
'''使用集合'''
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)
'''使用双指针'''
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(╥﹏╥)o,加油加油!
参考链接:https://www.bilibili.com/video/BV1ba411S7wu/?vd_source=0ca19358f6d978ba61da9f4823f97c25