Day06 代码随想录刷题
知识点:哈希表、列表、字典、集合、指针
一、LeetCode242. 有效的字母异位词
1.解题思路
考点为哈希表的多种实现方式和使用方式
2.代码实现
2.1 方法一:列表哈希
根据题意仅包含小写字母,其Unicode码点值为连续的26位,可以利用列表list创建一个哈希表HashList,先利用字符串s填充,再利用字符串t消除,若出现多消少消的情况,则证明不是异位词。
其中ord()可实现将变量转为Unicode码点的int值
class Solution:
#解法一:列表哈希
def isAnagram(self, s: str, t: str) -> bool:
#数据较少且连续,可用list实现,直接初始化指定szie的哈希表
HashList = [0]*26
#填充哈希表
for i in s:
HashList[ord(i) - ord('a')] += 1
#消除哈希表
for i in t:
HashList[ord(i) - ord('a')] -= 1
#检查哈希表
for i in range(26):
if HashList[i] != 0:
return False
return True
该解法有三个循环,前两个循环是遍历字符串,时间复杂度为O(n),最后一个是遍历哈希表,因为哈希表长度固定,故时间复杂度为O(1),最终该解法的时间复杂度为O(n);
该解法只使用了固定大小的额外空间来存储哈希表,不随输入规模的增长而增加空间消耗,所以空间复杂度 O(1)。
2.2 方法二:defaultdic字典
defaultdict 是 Python 标准库 collections 模块中的一个类,它是一个字典(dict)的子类。
defaultdict接受一个工厂函数作为参数,这个factory_function可以是list、set、str等等。作用是当key不存在时,返回的是工厂函数的默认值,比如list对应[],str对应的是空字符串,set对应set(),int对应0。
class Solution:
#解法二:defaultdict字典
def twoSum(self, nums: List[int], target: int) -> List[int]:
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
该解法开辟了两个字典,且分别遍历了一遍,其复杂度取决于列表nums的长度,故时间复杂度为O(n),空间复杂度为 O(n)。
2.3 方法三:Counter
Counter 是 Python 标准库 collections 模块中的一个类,用于计数可哈希对象的出现次数。它是一个无序的容器类型,可以用于快速计数和统计元素,即自动生成哈希字典。
可以通过向 Counter 构造函数传递一个可迭代对象(如列表、字符串等)来创建一个 Counter 对象。 Counter 会统计该可迭代对象中每个元素出现的次数,并以字典的形式存储,其中key是元素,value是对应的出现次数。
class Solution:
#解法三:Counter哈希字典
def isAnagram(self, s: str, t: str) -> bool:
from collections import Counter
# Counter返回的是一个哈希表字典
s_count = Counter(s)
t_count = Counter(t)
return s_count == t_count
该解法存在两个随nums变化的容器,创建和比较都是根据容器大小而定,所以时间复杂度为O(n),空间复杂度是 O(n)
二、LeetCode349.两个数组的交集
1.解题思路
考察哈希表的不同创建方式和使用,考察了列表、集合的特点
2.代码实现
2.1 方法一:字典哈希+列表
上一题讲到哈希字典,提到了defaultdict和Counter两种方式,这2种方式都是封装好可以直接用的dict。面对大小未知,且具有可选默认值的哈希表可通过dict.get(key,default_value)来填充。
本解法不能通过HashDict[num] += 1来填充,因为第一个HashDict[num] 不存在,他没有初始化为0,而HashDict.get(key,default_value)顺带进行了初始化。
class Solution:
#解法一:字典哈希+列表
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
#创建哈希字典
HashDict = {}
#用nums1填充哈希表
for num in nums1:
HashDict[num] = HashDict.get(num,0) + 1
resList = []
#将nums2的值与哈希表进行比较,重复值取出存入list后从哈希表中消除
for num in nums2:
if num in HashDict:
resList.append(num)
del HashDict[num]
#返回结果list
return resList
该解法创建并遍历了一个随nums1长度变化的哈希表,还遍历了nums2长度的列表,所以时间复杂度为O(n);所以空间复杂度 O(n)。
2.2 方法二:字典哈希+集合
上一解法通过列表进行存储交集元素,为了避免重复,每次存储后都在哈希表中进行了消除操作,方法二通过集合可避免重复,一步到位。
class Solution:
#解法二:字典哈希+集合
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
HashDict = {}
for num in nums1:
HashDict[num] = HashDict.get(num,0) + 1
#创建集合
resSet = set()
#将nums2的值和哈希表进行比较,重复值存入set
for num in nums2:
if num in HashDict:
resSet.add(num)
#转换格式后返回
return list(resSet)
该解法和解法二一样,故时间复杂度为O(n),空间复杂度为 O(n)。
2.3 方法三:列表哈希
利用哈希列表实现取交集操作
class Solution:
#解法三:列表哈希
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
#初始化哈希表
HashList1 = [0]*1001
HashList2 = [0]*1001
resList = []
#填充哈希表
for num in nums1:
HashList1[num] += 1
for num in nums2:
HashList2[num] += 1
#取交集
for i in range(1001):
if HashList1[i]*HashList2[i] > 0:
resList.append(i)
return resList
该解法存在两个哈希列表,哈希表创建时空间复杂度为O(1),遍历nums填充哈希表时时间复杂度为O(n),最后取交集操作遍历了哈希表,时间复杂度为O(1).虽然本题列表长度和元素范围都固定了,但是本解法还是倾向于认为时间复杂度为O(n),空间复杂度是 O(n)
2.4 方法四:列表哈希
直接利用集合取交集的操作(&)完成任务
class Solution:
#解法四:集合
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
return list(set(nums1) & set(nums2))
该解法将两个nums转化为集合,时间复杂度为O(n),取交集操作时间复杂度最大不超过O(n),故时间复杂度为O(n);创建了两个集合以及取交集三个空间,所以空间复杂度也为O(n)。
三、LeetCode202.快乐数
1.解题思路
无限循环意味着重复出现同样的平方和,可用哈希表记录,考点还是哈希表的创建和使用。
2.代码实现
2.1 方法一:集合
num, r = divmod(num,10)函数输入除数num和被除数10,返回商num和余数r。
class Solution:
#解法一:集合
def isHappy(self, n: int) -> bool:
#创建集合
resSet = set()
while n not in resSet:
#加入集合
resSet.add(n)
#更新n
n = self.getNext(n)
if n == 1:return True
#出现重复,退出 while 循环
return False
#将当前num按每位取平方和
def getNext(self, num):
nextNum = 0
while num:
num, r = divmod(num,10)
nextNum += r**2
return nextNum
两个函数分别说明:
getNext函数的空间复杂度为O(n),由于数字n最大有log(n) + 1位,所以getNext函数的时间复杂度可以表示为O(log(n));
isHappy函数的空间复杂度为O(k),其中k是最大的更新次数(k取决于输入数字n),时间复杂度为O(log(n))。
class Solution:
#解法一的精简版:集合
def isHappy(self, n: int) -> bool:
#创建集合
resSet = set()
while n != 1:
#出现重复,返回
if n in resSet:return False
#加入集合
resSet.add(n)
#按位求平方和,更新n
n = sum(int(i)**2 for i in str(n))
#出现数字1,结束循环
return True
空间复杂度为 O(n),时间复杂度为O(log(n))。
2.2 方法二:列表
和方法一类似,几乎一样
class Solution:
#解法二:列表
def isHappy(self, n: int) -> bool:
resList = []
while n not in resList:
#加入列表
resList.append(n)
#按位求平方和,更新n
nextNum = 0
n_str = str(n)
for i in n_str:
nextNum += int(i)**2
n = nextNum
if n == 1:return True
return False
空间复杂度为 O(n),时间复杂度为O(log(n)),其中 k 是更新次数的上限。
class Solution:
#解法二的精简版:列表
def isHappy(self, n: int) -> bool:
#创建列表
resList = []
while n != 1:
#出现重复,返回
if n in resList:return False
#加入列表
resList.append(n)
#按位求平方和,更新n
n = sum(int(i)**2 for i in str(n))
#出现数字1,结束循环
return True
空间复杂度为 O(k),时间复杂度为O(log(n)),其中 k 是更新次数的上限。
2.3 方法三:快慢指针
快乐数的按位求平方和出现循环这一情况与链表有环算法的思路类似。
class Solution:
#解法三:快慢指针
def isHappy(self, n: int) -> bool:
slow = n
fast = n
while self.getNext(fast) != 1 and self.getNext(self.getNext(fast)) != 1:
slow = self.getNext(slow)
fast = self.getNext(self.getNext(fast))
if slow == fast:
return False
return True
def getNext(self, num):
nextNum = 0
while num:
num, r = divmod(num,10)
nextNum += r**2
return nextNum
空间复杂度为O(1),对于这种方法,我们不需要哈希集来检测循环。指针需要常数的额外空间,时间复杂度为O(log(n))
四、LeetCode1.两数之和
1.解题思路
只找两个数,在先选定一个数value的情况下,另一个目标数(target - value)就已经确定了,在哈希表中寻找该目标数,若不存在则将当前value和索引存入哈希表,否则输出结果。
2.代码实现
2.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]
时间复杂度为O(n^2),空间复杂度 O(1)。
2.2方法二:字典
定义一个key为元素值,value为索引的反向哈希字典。
enumerate() 是一个内置函数,用于在迭代过程中同时获取索引和对应的元素。它接受一个可迭代对象作为参数,并返回一个由索引和元素组成的枚举对象。
class Solution:
#解法二:字典{value:index, ...}
def twoSum(self, nums: List[int], target: int) -> List[int]:
#创建字典
resDict = {}
#遍历nums的索引和元素值
for index,value in enumerate(nums):
#目标数在字典的key内,返回答案
if target - value in resDict:
return [resDict[target - value], index]
#目标数不在字典key内,当前遍历的数和索引加入字典
resDict[value] = index
return []
时间复杂度为O(n),最坏情况下可能涉及存储所有元素,故空间复杂度为O(n)。
2.3方法三:集合
解法三类似解法二的字典,不同之处在于定义了一个集合,用于存储元素值。最后通过对目标值求原list的index来找回去索引。
class Solution:
#解法三:集合(记录value,根据value再回list里面找索引)
def twoSum(self, nums: List[int], target: int) -> List[int]:
#创建集合
resSet = set()
#遍历nums的索引和元素值
for index, value in enumerate(nums):
temp = target - value
#目标数在集合内,返回答案
if temp in resSet:
return [nums.index(temp),index]
#目标数不在集合内,当前遍历的数加入集合
resSet.add(value)
时间复杂度为O(n),空间复杂度为O(n)。
2.4方法四:双指针
其中sorted()函数使用的是Timsort算法,是一种混合了归并排序和插入排序的稳定排序算法,Timsort 在平均情况下具有 O(nlogn) 的时间复杂度,其中 n 是待排序列表的长度。
class Solution:
#解法四:双指针
def twoSum(self, nums: List[int], target: int) -> List[int]:
#列表升序处理
nums_sorted = sorted(nums)
left = 0
right = len(nums) - 1
while left < right:
#计算当前两指针所指元素值之和
curRes = nums_sorted[left] + nums_sorted[right]
if curRes == target:
#满足两数之和的要求后求解各自index
left_index = nums.index(nums_sorted[left])
right_index = nums.index(nums_sorted[right])
#如果索引相同,代表list中存在2个元素值相同,且都是答案
if left_index == right_index:
right_index = nums[left_index + 1:].index(nums_sorted[right]) + left_index + 1
return [left_index, right_index]
#不满足两束之和则指针移动
elif curRes < target: left += 1
else: right -= 1
该解法先将数组排序好O(nlogn),再利用双指针法遍历一遍O(n)得到结果,其实方法nums.index()也是O(n),但最终时间复杂度还是O(nlogn), 为了保存下标信息另开了一个数组nums_sorted,所以空间复杂度为O(n)。
本文围绕LeetCode的四道题展开,包括有效的字母异位词、两个数组的交集、快乐数和两数之和。针对每道题给出多种解题思路和代码实现,如列表哈希、字典哈希、集合等方法,并详细分析了各解法的时间复杂度和空间复杂度,主要涉及哈希表、列表、字典等知识点。
1641

被折叠的 条评论
为什么被折叠?



