算法记录 | Day5哈希表基础
LeetCode 242-有效字母的异位词
题目描述:给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。
注意:若 s 和 t 中每个字符出现的次数都相同,则称 s 和 t 互为字母异位词。
输入: s = "anagram", t = "nagaram"
输出: true
解题思路
哈希表:判断是否有重复元素出现;考虑三种数据结构:数组;set:set和dict类似,也是一组key的集合,但==不存储value==,且key不能重复;map: map 是一个key value 的数据结构。( map 和 set 的 key 值不可更改。)
- 我的思考代码过程如下:① 将s中所有的字母加入hash表中,记录次数;② 判断 j 中的元素是否在hash表出现过,出现相减,未出现则加一。若最终结果都为0,说明次数一样为true,否则为false。
- 这种方法总共经历了3次for循环。
class Solution:
def isAnagram(self, s: str, t: str) -> bool:
hashmap = dict()
# 将s中所有的字母加入hash表
for i in s:
if i not in hashmap:
hashmap[i] = 1
else:
hashmap[i] += 1
# 判断 j 中的元素是否在hash表出现
for j in t:
if j not in hashmap:
hashmap[j] = 1
else:
hashmap[j] -= 1
for k in hashmap:
if hashmap[k] != 0:
return False
return True
方法二:也是利用哈希表记录次数,但记录方式不一样,通过把字符映射到数组也就是哈希表的索引下标上,因为字符a到字符z的ASCII是26个连续的数值,所以字符a映射为下标0,相应的字符z映射为下标25。
class Solution:
def isAnagram(self, s: str, t: str) -> bool:
record = [0] * 26
for i in range(len(s)):
#并不需要记住字符a的ASCII,只要求出一个相对数值就可以了
record[ord(s[i]) - ord("a")] += 1
print(record)
for j in range(len(t)):
record[ord(t[j]) - ord("a")] -= 1
for k in range(26):
if record[k] != 0:
return False
return True
注意:这里用的实际是数组来记录,用到了哈希表的思想。即数组可以是一种哈希表。
方法三:关于 collections 中的 defaultdict 方法
from collections import defaultdict
class Solution:
def isAnagram(self, s: str, t: str) -> bool:
s_dict = defaultdict(int)
t_dict = defaultdict(int)
for i in s:
s_dict[i] += 1
for j in t:
t_dict[j] += 1
return s_dict == t_dict
注意:
- defaultdict 与 dict 的区别
- ① defaultdict 括号里必须标明类型
- ② 如果访问字典dict中不存在的键,会引发
KeyError异常,所以我们要先处理没有元素的情况;collections 库中的 defaultdict 方法则可以直接写加的操作。
难点
- 注意三种hash表结构的选择,目前没有做过用set的题目,还不太清除具体操作。
总结
哈希表的解题方式:看题目中是否需要判断重复元素
LeetCode 349-两个数组的交集
给定两个数组 nums1 和 nums2 ,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2]
解题思路
- 暴力法
- 利用哈希表
方法一:暴力
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
# 暴力
res = []
for i in range(len(nums1)):
for j in range(len(nums2)):
if nums1[i] == nums2[j] and nums2[j] not in res:
res.append(nums2[j])
return res
方法二:哈希表
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
# 哈希法
record = dict()
res = []
# 若 nums1 = [3, 6, 2, 2, 1]
for i in range(len(nums1)):
if nums1[i] not in record:
record[nums1[i]] = 1
print(record) # {3: 1, 6: 1, 2: 1, 1: 1}
for j in range(len(nums2)):
if nums2[j] in record and nums2[j] not in res:
res.append(nums2[j])
return res
注意:
- 方法二与方法一相比,时间复杂度降低了。
- dict() 是按照出现元素的顺序进行存储的。
方法三: 哈希表set
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
return list(set(nums1) & set(nums2)) # 两个数组先变成集合,求交集后还原为数组
注意:
-
dict和set唯一区别在于没有存储value,但是原理一样,key的值是唯一的,不可改变。
-
set的主要用法可以判断一个数组里有哪些元素,比如set([3, 6, 2, 2, 1]) ,则输出{1,2,3,6} 。注意这里只是告诉你有1,2,3,6这四个元素,并不是说set是有序的。
难点
- 哈希表的选择
- 数组,可以直接通过下标访问元素。
- map,是一个 key value 的数据结构,适合记录次数。
- set,有 key 无value值。c++里set有不同的实现方式,包括哈希和红黑树两种,一般优先使用unordered_set,因为它的查询和增删效率是最优的(不太清楚)。python里set底层就是hash,可以不用考虑其他set的实现方法。
总结
这道题想法很简单,用多种解法解出来可以思考各个哈希表的应用场景,最难的应该是灵活使用到各个适合的场景之中。
LeetCode 202-快乐数
题目描述:编写一个算法来判断一个数 n 是不是快乐数。
「快乐数」 定义为:
- 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
- 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
- 如果这个过程 结果为 1,那么这个数就是快乐数。
如果 n 是 快乐数 就返回 true ;不是,则返回 false 。
输入: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:
# 计算各个数位的和
def sumHappy(n):
sum = 0
while n >= 10:
mod = n % 10
sum = sum + mod**2
n = n // 10
sum = sum + n**2
return sum
# 这样可能会进入很无限次循环
# 注意思考到什么情况下快乐数会出现无数次循环
ans = sumHappy(n)
record = set()
while True:
if ans == 1:
return True
else:
ans = sumHappy(ans)
if ans not in record:
record.add(ans)
else:
return False
难点
- ① 计算一个数各个数位的平方和
- ② 陷入无限次循环如何输出 False
- ==注意==思考到 当计算过程中出现重复数的时候会陷入到循环中
总结
这道题于我而言最难的点在于不知如何退出循环
LeetCode 1-两数之和
题目描述:给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1]
解题思路
- 暴力解法
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]:
record = dict()
# 存储下标和值
for key, value in enumerate(nums):
# record[key] = value
record[value] = key
print(record)
# 查找target - nums[i]的值
for i in range(len(nums)):
res = target - nums[i]
if res in record and i != record[res]:
return [i, record[res]]
else:
continue
注意:
- key 和 value的值不要写反了!!
- 字典是没有重复元素的,如果相等后面的元素会覆盖前面的元素!!所以不需要判断下标。
更简单的写法:
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
records = dict()
for index, val in enumerate(nums):
if target - nums[index] not in records:
records[val] = index
print(records)
else:
return [records[target - val], index]
难点
- key 和 value的值
- value值相同的时候如何找另一个key
总结
这次全部自己写一遍的时候,发现思路都还是挺简单的,就是书写过程中的一些小细节。
再次提醒自己:
- 哈希表注意三种结构的使用
- 数组,可以直接通过下标访问元素。
- map,是一个 key value 的数据结构,适合记录次数,无重复元素。
- set,有 key 无value值;适合查看有哪些无重复元素。
告诉大家,查找错误的时候放到本地进行debug,就能很快明白自己错在哪了!!