代码随想录算法训练营第七天| 454. 四数相加II,383. 赎金信,15. 三数之和 ,18. 四数之和
454. 四数相加II
题目链接:四数相加II
先吐槽一下这个题nums4[l]我还以为是nums4一直取1,想了半天为什么4要存在。
因为这个题是四个nums,所以不用考虑查虫的问题,所以直接当三数之和的哈希版做就好了。
首先制作一个哈希计算nums1和nums2能有几种和,每种和有几个。
然后用nums3和nums4遍历,去找0-nums3-nums4在上面的哈希表里存不存在。
两个比较幼稚的注意点:
- 如果存在的话加上的是这个和之前在nums1和nums2里出现过的次数,并不是直接+1
- 找到次数用的key是hash[-nums3[i]-nums4[j]]而不是hash[nums3[i]+nums4[j]],找错误找了好久没发现……
class Solution(object):
def fourSumCount(self, nums1, nums2, nums3, nums4):
"""
:type nums1: List[int]
:type nums2: List[int]
:type nums3: List[int]
:type nums4: List[int]
:rtype: int
"""
count=0
d=defaultdict(int)
for i in nums1:
for j in nums2:
d[i+j] += 1
for i in nums3:
for j in nums4:
if (0-i-j) in d:
count+=d[-i-j]
return count
383. 赎金信
题目链接:赎金信
这个题和昨天的哈希表都差不多,就是算一下每个字母出现了几次然后比较一下就好。
class Solution(object):
def canConstruct(self, ransomNote, magazine):
"""
:type ransomNote: str
:type magazine: str
:rtype: bool
"""
d1=defaultdict(int)
d2=defaultdict(int)
for i in magazine:
d1[i]+= 1
for i in ransomNote:
d2[i] += 1
for i in d2:
if i not in d1:
return False
elif d2[i] > d1[i]:
return False
return True
15. 三数之和
题目链接:三数之和
哈希
哈希的思路和二数之和是一样的:
例子:nums = [-1,0,1,2,-1,-4]
d = []用来存放遍历过的元素
一个for从nums[0]开始循环,一个for从nums[1]开始循环,然后在d里找是否存在(0-nums[0]-nums[1]),有的话就加到result里去,没有就在d里存一个nums[j],然后去下一个。
注意点:
- 有一个问题是当j从(i+1)到最后遍历完一次之后,d该怎么处理。我这里的做法是把d清空了然后只放已经遍历过得nums[i]进去,这样才能避免重复。
- 另一个问题是result的list里是要求不能有重复的,我的做法(可能是导致超时的原因)是每次放进去之前都排个序,这样重复的也可以找到。
自己写了一个在308 / 312的地方超时了。
class Solution(object):
def threeSum(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
result = []
d = []
l = []
for i in range(len(nums)):
l.append(nums[i])
for j in range((i+1),len(nums)):
a = 0-(nums[i]+nums[j])
if (a in d) and (sorted([nums[i],nums[j],a]) not in result):
result.append(sorted([nums[i],nums[j],a]))
d.append(nums[j])
d=[]
d = d + l
return result
双指针
双指针真的非常的巧妙
例子:nums = [-1,0,1,2,-1,-4]
首先进行排序,nums = [-4,-1,-1,0,1,2]
我们现在可以只要一个for循环,从0到list最后。
首先i走到nums[0]不动,然后安排两个指针,left和right,left指向nums[1]右边指向最后一个元素。
- 如果nums[i]+nums[left]+nums[right] == 0,那么放进result
- 如果nums[i]+nums[left]+nums[right] < 0,那么把left往左移动来加大。
- 如果nums[i]+nums[left]+nums[right] > 0,那么把right往右移动来减小。
注意点:
- 首先就是在for循环遍历的时候,如果有前面已经遍历过得元素就跳过,lizi里的第二个-1就可以跳过,看例子,如果在重复一次还是会再得到一个[-1,0,1],而且浪费时间,我这里是做了一个储存动作,感觉这一步也比较浪费时间。
- 还有一个减枝动作是,因为是排序的,nums[i]应该是这个三元组最小的那个,如果他都已经大于0了那么后面也不用看了。
- while left<right的时候,如果找到了==0的情况,一定要记得出循环啊,忘记出循环的我陷入了死循环。
- 不排除在nums[i]之后的list里有很多重复的元素,但由于我们一开始就给数组排序了,重复就是连顺序都重复了,那就可以直接去check是不是
if [nums[i],nums[left],nums[right]] in result
。
debug到崩溃的一道题,最后速度还是很慢也没有办法了QAQ
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
nums.sort()
#print(nums)
result = []
l = []
for i in range(len(nums)-2):
if (nums[i] in l) or (nums[i]>0):
continue
else:
left = i+1
right = len(nums)-1
while left < right:
if (nums[i]+nums[left]+nums[right] == 0) and ([nums[i],nums[left],nums[right]] not in result):
result.append([nums[i],nums[left],nums[right]])
left += 1
right -= 1
elif nums[i]+nums[left]+nums[right] < 0:
left += 1
else:
right -= 1
l.append(nums[i])
return result
一些无效剪枝
写完四数之和突然发现自己三数之和在做一些无效剪枝
if (nums[i] in l) or (nums[i]>0):
continue
这个位置其实应该是在nums[i]>0
的时候直接break掉的,最小的都大于target了后面就不用去想了。
if nums[i]>0:
break
if nums[i] in l:
continue
18. 四数之和
题目链接:四数之和
视频解析:四数之和
本来以为看了视频自己写也要干很久,但还行,推荐看视频解析,卡老师说的非常清楚。
双指针
这题和三数之和的做法是一样的,双指针怎么用还是看上一题,这题主要是说怎么剪枝和怎么去重。
- 关于去重,这里没有用第三题我自己那种浪费空间的做法,虽然不用动脑子,当感觉比较浪费,两个去重点
if (i>0) and (nums[i] == nums[i-1]):
和if (j>i+1) and (nums[j] == nums[j-1]):
最关键的地方在于i>0
和j>i+1
其实一开始我没有想明白,觉得i>0
和j>i+1
应该是天然成立的,写着没什么意义,但并不是,i=0
和j=i+1
就是开始重复的地方,第一次得做完,不然就是去重到自己都没了。 - 关于剪枝,这里的剪枝的问题就是直接
nums[i]>target
是剪不掉的,因为如果大家都是负的,那负的加负的就是更负,那是可以达到target的,所以这里不能剪,要加上大家都是正的条件才可以剪掉。同样参考三数之和的无效剪枝,这里应该是break
不是continue
。
class Solution:
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
nums.sort()
#[-2,-1,0,0,1,2,3,4]
result = []
for i in range(len(nums)-3):
if (nums[i]>0) and (target>0) and (nums[i]>target): #剪枝
break #这里是可以直接break的因为最小的都大于target再往后也没什么意义了。
if (i>0) and (nums[i] == nums[i-1]): #去重,j>0是因为j=0不用管
continue
for j in range((i+1),len(nums)-2):
if (nums[i]+nums[j]>0) and (target>0) and (nums[i]+nums[j]>target):
break
if (j>i+1) and (nums[j] == nums[j-1]):
continue
left = j+1
right = len(nums)-1
while left < right:
s = nums[i]+nums[j]+nums[left]+nums[right]
if (s == target) and ([nums[i],nums[j],nums[left],nums[right]] not in result):
result.append([nums[i],nums[j],nums[left],nums[right]])
left += 1
right -= 1
elif s < target:
left += 1
else:
right -= 1
return result