代码随想录算法训练营第七天 | 454.四数相加II 、383. 赎金信 、 第15题. 三数之和

终于懂了三数(四数)之和的去重原理!

 454.四数相加II 

文档讲解:代码随想录

暴力法会超出时间限制,时间复杂度是o(n^{^{4}}) 

class Solution:
    def fourSumCount(self, nums1: List[int], nums2: List[int], nums3: List[int], nums4: List[int]) -> int:
        count = 0
        for i in range(len(nums1)):
            for j in range(len(nums2)):
                for k in range(len(nums3)):
                    for n in range(len(nums4)):
                        if(nums1[i]+nums2[j]+nums3[k]+nums4[n]) == 0:
                            count +=1
        return count

这道题目是四个独立的数组,只要找到A[i] + B[j] + C[k] + D[l] = 0就可以,不用考虑有重复的四个元素相加等于0的情况

用数组来保存结果,还是会超出时间限制,原因是最后判断两两数组的和的时候会有重复判断的情况,count一直是以1为单位进行判断的

        
class Solution:
    def fourSumCount(self, nums1: List[int], nums2: List[int], nums3: List[int], nums4: List[int]) -> int:
        #分解问题,四个数相加分解为两个数相加,先算出来两个数组的和有哪些情况,再进行组合
        sum1=[]
        sum2=[]
        count = 0
        for i in range(len(nums1)):
            for j in range(len(nums2)):
                sum1.append(nums1[i]+nums2[j])
        for i in range(len(nums3)):
            for j in range(len(nums4)):
                sum2.append(nums3[i]+nums4[j])
        for i in sum1:
            
            # if -i in sum2: #不可以这样写,因为-i在sum2中可能有很多个,这样只是加了一个
            #     count +=1
            for j in sum2:
                if j == -i:
                    count=count+1
        return count

改进:用字典来保存和与次数,相同的数判断一次就可以,count会增加很多,而不是1

       
class Solution:
    def fourSumCount(self, nums1: List[int], nums2: List[int], nums3: List[int], nums4: List[int]) -> int:        
        sum1={}
        sum2={}
        count = 0
        for i in nums1:
            for j in nums2:
                sum1[i+j] = sum1.get((i+j),0) + 1 #注意这个写法,还挺常用的
        for i in nums3:
            for j in nums4:
                sum2[i+j] = sum2.get((i+j),0) + 1
        for i in sum1:
            
            if -i in sum2: 
                count += sum1[i] * sum2[-i] #这里count增加的不再是1了
        print(sum1)
            
        return count

 383. 赎金信  

文档讲解:代码随想录

class Solution:
    def canConstruct(self, ransomNote: str, magazine: str) -> bool:
        a = [0] * 26
        for i in magazine:
            a[ord(i) - ord('a')] +=1
        for j in ransomNote:
            a[ord(j) - ord('a')] -=1
        ##只要a中不出现负数,就证明magazine中的字母足以覆盖ransomNote中的字母
        return all(x >= 0 for x in a)

 第15题. 三数之和

文档讲解:代码随想录

 说到去重,其实主要考虑三个数的去重。 a, b ,c, 对应的就是 nums[i],nums[left],nums[right]

首先是对a的去重:假设不去重,第一次遍历到a时,固定 i ,left和right会遍历完i之后的数,并且寻找和为-a的组合,这一步没有问题; 接下来遍历到i+1,对应的值也为a,同理,left和right会遍历完i+1之后的数,并且寻找和为-a的组合,这个遍历的区间相较于第一次遍历到a时(对应下标为i)的区间更小,也就是第一次遍历时找到的组合已经包括第二次遍历到a时找到的组合了。也就会导致重复,所以要对a去重。

其次是对b和c的去重:这时a已经是固定的了,假设不对b去重,那么接下来要找的c就是值为-(a+b)的值,那么如果b有重复的,就会导致找到相同的c,也就会导致最终有重复的组合。c的去重同理。

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        #先进行排序,非降序
        res =[]
        nums.sort()
        #从左开始遍历
        for i in range(len(nums)):
            
            #剪枝,如果第一个数已经大于0了就不需要往下判断了
            if nums[i] > 0:
                return res
            #去重
            if i > 0 and nums[i] == nums[i-1]:
                continue
            left = i + 1
            right = len(nums) - 1
            while left < right:
                if (nums[i]+nums[left]+nums[right]) > 0:
                    right = right -1
                elif (nums[i]+nums[left]+nums[right]) < 0:
                    left = left + 1
                else:
                    res.append([nums[i],nums[left],nums[right]])
                    #对left去重
                    while left<right and  nums[left] == nums[left+1]:
                        left = left + 1
                    #对right去重
                    while left<right and  nums[right] ==  nums[right - 1]:
                        right = right - 1
                    left = left + 1
                    right = right - 1
        return res

 第18题. 四数之和

文档讲解:代码随想录

 这个题与上面一个题的区别是:

①寻找四数之和,那么就要固定2个,然后再遍历left和right

②目标值不再是0了。不要判断nums[k] > target 就返回了,三数之和 可以通过 nums[i] > 0 就返回了,因为 0 已经是确定的数了,四数之和这道题目 target是任意值。比如:数组是[-4, -3, -2, -1]target-10,不能因为-4 > -10而跳过。因为目标值可能是负数,本质上就是再加上一个数有可能导致和是减少的,上一题中。如果当前值大于0,暗示了后面的都是正数了,加起来肯定会增加。这会导致剪枝操作有变化

注意:去重的操作与三数之和差不多,剪枝的地方要注意,尤其是对j的剪枝

class Solution:   
    def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
        nums.sort()
        res = []
        for i in range(len(nums)):
            #对i的剪枝,nums[i] 如果大于0且大于target了,那么数组后面的值都会大于target且大于0
            if nums[i] > target and nums[i]>0:
                return res
            ###对i去重
            if i >0 and nums[i] == nums[i - 1]:
                continue
            for j in range(i+1,len(nums)):
                #对j剪枝,nums[i]+nums[j]大于0并且大于target了,那么nums[j]肯定大于0,从j开始后面都是0,也就不用再
                #遍历后面的left和right了
                #但是不能直接return,这里只是说明j已经遍历到数组的尾部了,但是i还要继续遍历
                if nums[i]+nums[j] > target and target>0:  #这里有问题,需要注意要break而不是return
                    # return res
                    break
                #对j去重,j要大于i+1才可以,i+1是j遍历的第一个数,从第二个开始判断
                if j > i+1 and nums[j] == nums[j-1]:
                    continue
                left = j + 1
                right = len(nums) - 1 
                while left < right:
                    if nums[i]+nums[j]+nums[left] + nums[right]>target: #注意这里是target
                        right = right - 1
                    elif nums[i]+nums[j]+nums[left] + nums[right]<target:
                        left = left + 1
                    else:
                        res.append([nums[i],nums[j],nums[left],nums[right]])
                        #对left的去重
                        while left <right and nums[left] == nums[left+1]:
                            left = left + 1
                        ##对right的去重
                        while left <right and nums[right] == nums[right-1]:
                            right = right - 1
                        left = left + 1
                        right = right - 1
        return res

  • 15
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值