n数之和问题——“排序+双指针”大法

最近在力扣上刷题,一连刷到了好多n数之和的题,做完后把它们放在一起比较分析了一下,发现其实n数之和问题似乎都有这样的一个思想贯彻其中——“排序+双指针”,下面就让我们通过这些例题来一起感受解决这类问题的方法吧!

例题

1.两数之和

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target  的那 两个 整数,并返回它们的数组下标。你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。你可以按任意顺序返回答案。

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
       for i in range(0,len(nums)-1):
            for j in range(i+1,len(nums)):
               if nums[i] + nums[j] ==target:
                   return [i,j]

两数之和还是非常简单的,用两个循环遍历寻找就行了,双指针还没有用武之地。

2.三数之和

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        n = len(nums)
        nums.sort()
        ans = []
        ln, rn = 0, n-1  # 左指针和右指针的初始位置分别为数组的首尾
        for i in range(ln, rn-1):  # 循环遍历数组中的每个数字
            if i > 0 and nums[i] == nums[i-1]:  # 跳过重复的数字
                continue
            target = -nums[i]  # 将三数之和转化为两数之和的问题
            ln = i + 1
            rn = n - 1
            while ln < rn:
                if nums[ln] + nums[rn] == target:  # 找到满足条件的三元组
                    ans.append([nums[i], nums[ln], nums[rn]])
                    while ln < rn and nums[ln+1] == nums[ln]:  # 跳过重复的数字
                        ln += 1
                    while ln < rn and nums[rn-1] == nums[rn]:  # 跳过重复的数字
                        rn -= 1
                    ln += 1
                    rn -= 1
                elif nums[ln] + nums[rn] < target:  # 两数之和小于目标值,将左指针右移一位
                    ln += 1
                else:  # 两数之和大于目标值,将右指针左移一位
                    rn -= 1
        return ans

三数之和肉眼可见地复杂了很多,这时候就到了“排序+双指针”大法大显身手的时候啦!首先给整个数组排序,排序是为了可以通过比较大小来控制左右指针的移动。双指针一下子解决了两数,剩下的一个数字只要通过循环就可以解决,比较now和target的大小来控制左右指针的移动,最后得出结果。

3.最接近的三数和

给你一个长度为 n 的整数数组 nums 和 一个目标值 target。请你从 nums 中选出三个整数,使它们的和与 target 最接近。返回这三个数的和。(假定每组输入只存在恰好一个解)

class Solution:
    def threeSumClosest(self, nums: List[int], target: int) -> int:
        n=len(nums)
        ans = inf
        nums.sort()
        for i in range(n-2):
            ln,rn=i+1,n-1
            while ln<rn:
                may=nums[i]+nums[ln]+nums[rn]
                goal=target-nums[i]
                now=nums[ln]+nums[rn]
                ans=ans if abs(target-ans)<abs(target-may)else may
                if now==goal:
                    return target
                if now<goal:
                    ln+=1
                else:
                    rn-=1
        return ans

可以看到最接近的三数和与三数之和异曲同工,无非是把等于转换成了距离(差的绝对值)的比较,其余都基本一致。

4.四数之和

给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):0 <= a, b, c, d < n,a、b、c 和 d 互不相同,nums[a] + nums[b] + nums[c] + nums[d] == target(你可以按任意顺序返回答案 )

class Solution:
    def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
        res = []
        nums.sort()
        n  = len(nums)
        for i in range(n):
            # 确定第一个数
            if i > 0 and nums[i - 1] == nums[i]:
                continue    # 找到下一个不一样的值,去重
            # 确定第二个数
            for j in range(i + 1, n):
                if j > i + 1 and nums[j - 1] == nums[j]:
                    continue    # 找到下一个不一样的值,去重
                # 寻找剩下的两个数
                left = j + 1
                right = n - 1
                two_number_target = target - nums[i] - nums[j]
                while left < right:
                    two_number_sum = nums[left] + nums[right]   # 两数之和
                    if two_number_sum == two_number_target:
                        res.append([nums[i], nums[j], nums[left], nums[right]]) #找到一组
                        left += 1   # 左指针先右移再去重
                        right -= 1  # 右指针先左移再去重
                        while left < right and nums[left] == nums[left - 1]: left += 1      # 找到下一个不一样的值,去重
                        while left < right and nums[right] == nums[right + 1]: right -= 1   # 找到下一个不一样的值,去重
                    elif two_number_sum < two_number_target:
                        left += 1       # 两数之和小于两数目标值,left增大,增大加数
                    else:
                        right -= 1      # 两数之和大于两数目标值,right减小,减小加数
        return res

可以看到四数之和更加复杂,但是万变不离其宗,还是祭出我们的“排序+双指针”大法,干就完了!

总结

其实可以发现,所谓的“排序+双指针”大法其实就是通过引入双指针的方法来代表其中的两个数,这样避免了多层循环产生浪费,提高了效率。双指针确实是个非常实在且好用的方法,值得我们大家好好琢磨钻研,掌握并且熟练运用。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值