Day07 代码随想录刷题
知识点:哈希表、指针
一、LeetCode454. 四数相加II
1.解题思路
考点为哈希表字典的用法
根据题意,提供四个有序列表,求从各列表中取出一个数的和为0,问有几种元组取法。因为元组tuple类似于只读列表list,所以不会出现重复,不需要去重。
2.代码实现
2.1 方法一:普通字典
class Solution:
#解法一:普通字典
def fourSumCount(self, nums1: List[int], nums2: List[int], nums3: List[int], nums4: List[int]) -> int:
#新建字典
hashDict = {}
#遍历前2个列表各元素求和(key)与各种和的取法(value)存入字典
for x1 in nums1:
for x2 in nums2:
hashDict[x1 + x2] = hashDict.get(x1 + x2, 0) + 1
#遍历后2个列表个元素求和后的互补temp是否在字典key里,是的话,对其取法value进行计数
count = 0
for x3 in nums3:
for x4 in nums4:
temp = 0 - (x3 + x4)
if temp in hashDict:
count += hashDict[temp]
return count
2.2 方法二:defaultdict字典
利用hashDict.get(-(i + j), 0)
直接取出字典的value
,一步到位,因为如果不存在value
,就取到0
,不影响计数结果
class Solution:
#解法二:default字典
def fourSumCount(self, nums1: List[int], nums2: List[int], nums3: List[int], nums4: List[int]) -> int:
#新建字典
hashDict = defaultdict(int)
for i in nums1:
for j in nums2:
hashDict[i + j] += 1
count = 0
for i in nums3:
for j in nums4:
count += hashDict.get(-(i + j), 0)
return count
两种解法逻辑一样,都执行了两次两层嵌套的循环,所以时间复杂度是O(n^2)
;额外使用了一个哈希字典来保存前两个列表的元素求和以及对应的取法次数。字典的大小取决于列表1和列表2中元素求和的唯一值的数量,最坏情况下可以达到O(n^2)
。所以,解法的空间复杂度是O(n^2)
。
二、LeetCode383.赎金信
1.解题思路
本题很类似之前学的LeetCode242.有效的字母异位词,用一个列表list
装magazine
的元素,再遍历ransomNote
去消除容器中的元素,如果出现没得消则返回False
。
2.代码实现
2.1 方法一:哈希列表
class Solution:
#解法一:列表
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
hashList = [0]*26
for x in magazine:
hashList[ord(x) - ord('a')] += 1
for y in ransomNote:
hashList[ord(y) - ord('a')] -= 1
for i in range(26):
if hashList[i] < 0:return False
return True
2.2 方法二:defaultdict哈希字典
利用字典先装magazine
的全部元素和个数,再遍历ransomNote
,将其中出现的元素在字典中消去,若字典里没有该元素可消则返回False
。
class Solution:
#解法二:defaultdict
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
hashDict = defaultdict(int)
for x in magazine:
hashDict[x] += 1
for x in ransomNote:
temp = hashDict.get(x)
if not temp:
return False
else:
hashDict[x] -= 1
return True
方法一和方法二一样,两个字符串分别都遍历一次,且创建了一个字典容器,所以时间复杂度和空间复杂度都是O(n)
2.3 方法三:Counter
再次加强记忆,Counter
是将可哈希的容器元素变成一个字典,返回值是字典。
字典的相减:
1.减数和被减数存在同一个
key
,则对应的value
相减,如果差值为正值(> 0
),则结果里保留该key
,且对应value
为差值;如果差值<= 0
,则结果不保留该key
。2.如果减数内没有被减数中的某个
key
,则结果中被减数的这个key
和对应的value
依旧保留。3.如果减数内存在被减数内不存在的
key
,即被减数没有key
去减减数的key
,结果依旧不会出现这个key
和对应的value
。
所以说,针对本题我们只关注ransomNote
里的全部元素是否都存在于magazine
,当字典ransomNote
减字典magazine
时,只会出现:
1.
ransomNote
中有,magazine
中也有,但是magazine
中的多,则差值为负数,取not
后为True
2.
ransomNote
中有,magazine
中也有,但是magazine
中的少,则差值为正数,取not
后为False
3.
ransomNote
中有,magazine
中也有,两者一样多,则差值为0
,取not
后为True
4.
ransomNote
中有,magazine
中没有,对应的key
的差值为存在,取not
后为False
class Solution:
#解法三:Counter
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
from collections import Counter
return not Counter(ransomNote) - Counter(magazine)
Counter
构建字典本身就需要时间复杂度为O(n)
,最坏情况下空间复杂度也是O(n)
;
2.4 方法四:count计数法
对于ransomNote
中的每个元素依次在两个列表中计数,如果ransomNote
中的数量 <=
magazine
中的数量,则为True
class Solution:
#解法四:count计数法
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
return all(ransomNote.count(c) <= magazine.count(c) for c in set(ransomNote))
遍历一个set
并且每一步都通过count
来遍历列表,所以该解答时间复杂度为O(n^2)
;因为创建了一个set
存储ransomNote
,空间复杂度是 O(n)
。
三、LeetCode15.三数之和
1.解题思路
提供一个列表,求三数之和为0
的元素不重复取法结果。当列表元素升序后,我们只需要先确定好一个值num[i]
,那么另外两个值就可以通过left
和right
两个指针在剩余列表部分里向中间逼近找到答案。列表升序,且第一个值若重复则跳过,这一操作可实现避免重复。
2.代码实现
2.1 方法:字典+双指针
class Solution:
#解法:字典+双指针
def threeSum(self, nums: List[int]) -> List[List[int]]:
#升序操作,无返回值,确保了最后的元组不会因为顺序不同而重复
nums.sort()
#创建字典
resDict = {}
#开始遍历列表
for i in range(len(nums)):
#最小元素已经超过结果
if nums[i] > 0:
break
#元素重复直接跳过
if i > 0 and nums[i] == nums[i - 1]:
continue
#双指针
left = i + 1
right = len(nums) - 1
while left < right:
temp = nums[i] + nums[left] + nums[right]
if temp < 0: left += 1
elif temp > 0: right -= 1
else:
#符合要求的元素值拼成元组后存入字典
key = (nums[i], nums[left], nums[right])
resDict[key] = resDict.get(key,0) + 1
#指针向中间逼近
left += 1
right -= 1
#将字典key全部取出组装成lsit返回
return list(resDict.keys())
列表排序时间复杂度是O(nlogn)
,然后开始循环遍历,外层循环需要遍历 n
次,内层循环使用双指针,左指针从 i+1
开始,右指针从末尾开始,需要遍历 n
次。
综上所述,该算法的时间复杂度为 O(nlogn + n^2) = O(n^2)
。
结果字典 resDict
最多刚好全部储存,所以算法的空间复杂度为 O(n)
。
四、LeetCode18.四数之和
1.解题思路
和上一题类似,但是我们需要双重循环来确定两个值,最后再用双指针找到解。不同之处在于本题的target
不再是0
,对于target
为负数的情况,需要特殊处理。
2.代码实现
2.1方法:字典+双指针
class Solution:
#解法:双指针+字典
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
#升序操作,创建字典
nums.sort()
resDict = {}
#双层遍历,注意去重操作
for i in range(len(nums)):
#索引i去重
if i > 0 and nums[i] == nums[i - 1]:
continue
for j in range(i + 1, len(nums)):
if nums[i] + nums[j] > target and target > 0:
break
#索引j去重
if j > i + 1 and nums[j] == nums[j - 1]:
continue
left = j + 1
right = len(nums) - 1
while left < right:
temp = nums[i] + nums[j] + nums[left] + nums[right]
if temp < target:
left += 1
elif temp > target:
right -= 1
else:
#升序操作使得相同的解其元素摆放位置一定相同,利用字典避免了list.append的相同元素重复添加
resDict[(nums[i], nums[j], nums[left], nums[right])] = True
left += 1
right -= 1
return list(resDict.keys())
时间复杂度为O(n^3)
,空间复杂度 O(n)
。