代码随想录第七天 | 454.四数相加II | 383. 赎金信 | 15. 三数之和 | 18. 四数之和

本文详细分析了LeetCode中的四数之和、三数之和等类似问题,通过哈希表、双指针技巧以及剪枝优化,探讨了如何降低时间复杂度,重点讲解了排序和去重在优化算法中的应用。
摘要由CSDN通过智能技术生成

454.四数相加II

Leetcode 原题链接

依旧采用哈希表的思路,用字典的key来储存数值,用字典的value来储存和为对应的key的组合出现频次。

我们分为两组,字典存一组,另一组和字典进行比对。
这样的话情况就可以分为三种:

  1. 字典存一个数组,如 A。然后计算三个数组之和,如 BCD。时间复杂度为:O(n)+O(n^3)得到O(n^3).
  2. 字典存三个数组之和,如 ABC。然后计算一个数组,如 D。时间复杂度为:O(n^3)+O(n)得到O(n^3).
  3. 字典存两个数组之和,如AB。然后计算两个数组之和,如 CD。时间复杂度为:O(n^2)+O(n^2),得到 O(n^2)

因此我们自然要选择时间复杂度最小的,也就是各自两两分组。先统计AB组的数字之和出现的频率,再计算CD会出现的数字之和。理论上讲,应当是按照排列组合的方法,将AB和CD这两队相反数出现频率相乘。但由于代码实现部分,是通过两个for循环遍历两个数组,因此每当在字典里存在num3+num4的相反数时,每得到一个num3+num4,只需要count加上对应相反数的频率,重复如此,实际上就是把相乘分成一个一个相加的道理。

class Solution:
    def fourSumCount(self, nums1: List[int], nums2: List[int], nums3: List[int], nums4: List[int]) -> int:
        dict={}
        for num1 in nums1:
            for num2 in nums2:
                dict[num1+num2]=dict.get(num1+num2,0)+1 # 统计各数字之和的出现频率
        count=0
        for num3 in nums3:
            for num4 in nums4:
                if -num3-num4 in dict:
                    count+=dict[-num3-num4] # 由于是循环,实际上就是each*对应频次,最后的count就是总的频次
        return count                        

383. 赎金信

Leetcode 383 原题链接
本题的思路跟242.字母异位词相似,区别就是本题要确保magazine有的字符包含了ransomNote的字符,并且字符数量也大于它。

# 数组
class Solution:
    def canConstruct(self, ransomNote: str, magazine: str) -> bool:
        rcount=[0]*26 # 不初始化的话会out of range
        mcount=[0]*26 # 如果只设置等于[],会导致数据不按照索引顺序计入,out of range
        for i in ransomNote:
            rcount[ord(i)-ord('a')]+=1
        for i in magazine:
            mcount[ord(i)-ord('a')]+=1
        return all(rcount[i] <= mcount[i] for i in range(26)) # 对每个字母对应的数量进行比较,如果符合,就返回True。采用all,如果有一个False,那么也不会返回True。
class Solution:
    def canConstruct(self, ransomNote: str, magazine: str) -> bool:
        record=[0]*26
        for i in ransomNote:
            record[ord(i)-ord('a')]+=1
        for i in magazine:
            record[ord(i)-ord('a')]-=1 # 用计入的数组抵消
        for i in range(26):
            if record[i]>0: return False # 说明有的字母r比m多,m不能全包含r
        return True

Counter的用法:对ransomNote没有的索引,即字母,Counter不会重新创建一个magazine独有的字母和其结果,只会针对ransomNote有的字母处理。即m有“z",而r没有,也不会产生新的字典[z: -1]。

class Solution:
    def canConstruct(self, ransomNote: str, magazine: str) -> bool:
        from collections import Counter
        return not Counter(ransomNote)-Counter(magazine)
        # 对ransomNote没有的索引,即字母,Counter不会重新创建一个magazine独有的字母和其结果,只会针对ransomNote有的处理

15. 三数之和

Leetcode 15 原题链接

哈希法

两层for循环就可以确定a和b的数值了,可以使用哈希法来确定 0-(a+b) 是否在 数组里出现过,其实这个思路是正确的,但是我们有一个非常棘手的问题,就是题目中说的不可以包含重复的三元组。哈希法再去重会导致这题非常费时,很容易超时。时间复杂度可以做到O(n^2),但还是比较费时的,因为不好做剪枝操作。

双指针

才用双指针的思路,首先将数组排序,然后有一层for循环,i从下标0的地方开始,同时定一个下标left 定义在i+1的位置上,定义下标right 在数组结尾的位置上。在数组中找到 abc 使得a + b +c =0,我们这里相当于 a = nums[i],b = nums[left],c = nums[right]。当sum偏大时,左移right;当sum偏小时,右移left。

正如思考哈希法所要考虑的,去重要怎么做呢?由于我们对数字进行了排序,因此我们需要考虑三个地方的去重,即i,left,right各考虑一次,当我们第一次碰到一个数字后,判断完条件,移动到下一个数字,这个时候如果下一个数字和上一个数字一样,那么我们也就没必要再去经历这个循环,自然就跳出循环进入到下一个数字。因此需要判定num[x]==num[x-1],这里的x可以是i,left,right。由于x-1可能导致out of range,我们自然要再加一个x>0的条件。

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        result=[]
        nums=sorted(nums)
        for i in range(len(nums)):
            if nums[i]>0: return result
            if i>0 and nums[i-1]==nums[i]: 
            	continue # 去重,因为nums[i-1]已经在上一轮算过了nums[i]
            left=i+1 # left在i的下一位
            right=len(nums)-1
            while left<right:
                if nums[i]+nums[left]+nums[right]>0:
                    right-=1
                elif nums[i]+nums[left]+nums[right]<0:
                    left+=1
                else: 
                    result.append([nums[i],nums[left],nums[right]])
                    while left<right and nums[right]==nums[right-1]:
                        right-=1 
                    while left<right and nums[left]==nums[left+1]:
                        left+=1 
                    # 三个指针去重
                    right-=1
                    left+=1
                    # 计入result后,要在缩小范围,因为如果只缩小一边,sum都不会是target,因此直接减少循环,直接两边都缩小
        return result

18. 四数之和

Leetcode 18 原题链接

思路

本题依旧采用双指针思路,解法是两层for循环nums[k] + nums[i]为确定值,依然是循环内有left和right下标作为双指针,找出nums[k] + nums[i] + nums[left] + nums[right] == target的情况,三数之和的时间复杂度是O(n^2),四数之和的时间复杂度是O(n^3)

那么一样的道理,五数之和、六数之和等等都采用这种解法。对于15.三数之和,双指针法就是将原本暴力O(n^3)的解法,降为O(n^2)的解法,四数之和的双指针解法就是将原本暴力O(n^4)的解法,降为O(n^3)的解法。

剪枝操作

剪枝(Pruning)是一种优化技术,用于减少搜索空间,提高算法的效率。在回溯算法或其他搜索算法中,剪枝技术通过判断某些分支或路径的无效性,从而避免不必要的搜索,以减少计算量。

本题相比于三数之和,思路多了一层for,但要多注意剪枝操作。第一层for循环中,由于我们对nums进行了排序,因此,只要第一个数nums[i]>0 and nums[i]>target and target>0,那么就说明后续不会再有符合条件的组合了,可以直接return result;但是在第二层循环中,即使nums[i]+nums[k]>0 and nums[i]+nums[k]>target and target>0,也可能因为所给数组存在负数,导致nums[i]+nums[k]是正负相加大于了target,如果这时直接return result,会无法使得i进入到绝对值小的正数中,即nums=[-2,0,0,0,1,7,9],target=1,由于-2+7>1,这个时候直接return result会导致-2之后的[0,0,0,1]没有被记录,因此这里不能return result而是应该用break来跳出循环!!!

class Solution:
    def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
        i=0
        result=[]
        nums.sort()
        for i in range(len(nums)):
            if nums[i]>0 and nums[i]>target and target>0:
                return result
                # break也行,但是会浪费搜索空间,因为后续肯定没有我们想要的结果了
            if i>0 and nums[i]==nums[i-1]: # 去重
                continue
            for k in range(i+1,len(nums)):
                if nums[i]+nums[k]>0 and nums[i]+nums[k]>target and target>0:
                    # 如果直接return result会有问题,因为本题target存在正负混合,如果直接返回会因为没有经过绝对值小的正数而错过符合条件的列表
                    break
                if k>i+1 and nums[k]==nums[k-1]:
                    continue
                left=k+1
                right=len(nums)-1
                while left<right:
                    sum=nums[i]+nums[k]+nums[left]+nums[right]
                    if sum<target: left+=1
                    elif sum>target: right-=1
                    else: 
                        result.append([nums[i],nums[k],nums[left],nums[right]])
                        while left<right and nums[right]==nums[right-1]:
                            right-=1
                        while left<right and nums[left]==nums[left+1]:
                            left+=1
                        right-=1
                        left+=1
        return result
  • 20
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值