今天补了上上次的最后一道题和昨天的全部,“正经人谁写日记啊”,以下是正正经经的day7。
第454题.四数相加II
所思所想:
1.这道题很特别,运用了分治法的思想,我认为暴力法的时间复杂度是n的4次方,但代码随想录认为是3次方,也有可能是我错了,欢迎大家指正,但是改进版是n的平方,我也如此认为。
2.具体做法就是把四个数组分为两组,ab,cd各一组,将ab组遍历得到各种可能加和的哈希表,再带着该哈希表,去遍历cd。
3.由于题目本身要求,返回值是所有可能满足加和为target的排列组合的个数(所以在算ab组的哈希表时,计数是必要的)
答案如下:
class Solution(object):
def fourSumCount(self, nums1, nums2, nums3, nums4):
table = {}
for num1 in nums1:
for num2 in nums2:
table[num1 +num2] = table.get(num1 +num2,0) + 1
count = 0
for num3 in nums3:
for num4 in nums4:
if -(num3 +num4) in table:
count+=table[-num3-num4]
return count
但其实我第一遍做错了,虽然验证过了,但是一提交复杂的例子没过,原因在于倒数第二行我写的是:
count+=1
而答案应该是:
count+=table[-num3-num4]
区别在于,最后统计的是不同排列组合。
第15题. 三数之和
个人认为这题比较讨人厌,不想深度思考了,答案用了三指针的方法,理解性默写一遍就得了。
1.返回结果过早:在找到一组和为 0 的三元组后,代码直接使用 return
语句返回该三元组,而没有继续寻找其他可能的三元组。
2.未处理重复元素:代码没有处理列表中可能存在的重复元素,这可能会导致结果中出现重复的三元组。
3.返回 []
的位置有误:代码在当前 index
对应的循环结束后就直接返回 []
,这意味着只会检查第一个元素,而不会继续检查后续元素
这是豆包给我原创代码的修改意见,非常切中要害。
class Solution(object):
def threeSum(self, nums):
result =[]
nums.sort()
for i in range(len(nums)):
left = i+1
right = len(nums)-1
if nums[i] > 0:
return result
elif i>0 and nums[i] == nums[i-1]:
continue
while left <right:
if nums[i]+nums[left]+nums[right]<0:
left+=1
elif nums[i]+nums[left]+nums[right]>0:
right-=1
elif nums[i]+nums[left]+nums[right]==0:
result.append([nums[i],nums[left],nums[right]])
while left <right and nums[left]==nums[left+1]:
left+=1
while left <right and nums[right]==nums[right-1]:
right-=1
left+=1
right-=1
return result
对于这个正确答案,我改了好几遍,有以下几个改动点值得反思
1.elif i > 0 这句很重要,直接影响了【0,0,0】等相同特点的元组,反过来说,也会越界,但没报错,不知道为什么。
2.这道题其实最主干的思想在最后一个elif中,这段代码百分之八十的字符数都在做剪枝操作。
383. 赎金信
之前的哈希表都是偏理论,这题算是一个结合实际的小应用题,挺有意思,也很简单,昭示了哈希表的应用场景。
第18题. 四数之和
class Solution(object):
def fourSum(self, nums, target):
result =[]
nums.sort()
length=len(nums)
for i in range(length):
if i > 0 and nums[i]==nums[i-1]:
continue
for j in range(i+1, length):
if j > i+1 and nums[j]==nums[j-1]:
continue
left = j + 1
right = length -1
while left < right:
sum = nums[i]+nums[j]+nums[left]+nums[right]
if sum < target:
left+=1
if sum > target:
right-=1
if sum == target:
result.append([nums[i],nums[j],nums[left],nums[right]])
while left<length-1 and nums[left]==nums[left+1]:
left+=1
left+=1
while right>=0 and nums[right]==nums[right-1]:
right-=1
right-=1
return result
我尽可能没有用剪枝操作,展现了必要过程,而没有追求极致的运行时间。
对于这段代码我有几个思考:
1.
if i > 0 and nums[i] == nums[i-1]:# 去重
continue
if j > i+1 and nums[j] == nums[j-1]: # 去重
continue
在思考这两句的时候,我觉得应该换一种perspective,不要去考虑大于多少多少就不行了,而是相同的数就放参与组合一次,也就是只有i=0,j=i-1,再加上后边的条件会导致continue也就是跳过。
2.
while left < right and nums[left] == nums[left+1]:
left += 1
while left < right and nums[right] == nums[right-1]:
right -= 1
答案是这么写的,首先第一遍写的时候我压根就没想通该怎么去重,单看答案之后觉得很疑惑,为什么呢?left<right为什么是判断条件,其实是反直觉的,因为一整个大循环体的准入条件就是left<right,但这道题让我体会到,这两者并没有直接关系,因为left,right的值靠的是语句,不是自动的。而且这里其实暗含了一种剪枝。
while left<length-1 and nums[left]==nums[left+1]:
left+=1
left+=1
while right>=0 and nums[right]==nums[right-1]:
right-=1
right-=1
这样才是纯去重,上边其实有点添油加醋了,但添油加醋可以少循环几次,节省时间。
总结
这章说是哈希,但其实哈希理解了以后,至少这几道不难,但是三数之和和四数之和用指针,很复杂,首先去重的逻辑不简单,其次考虑剪枝更复杂,还需熟能生巧。辛苦了!