力扣python实现-双指针

博客介绍了如何利用双指针优化算法解决三数之和与四数之和的问题。针对15.三数之和,通过有序数组和双指针避免了重复和提高了效率。对于18.四数之和,同样应用双指针,并增加了剪枝操作以减少无效循环。文章强调了循环过程中下标递增和元素不重复的重要性,并提供了完整的优化代码示例。
摘要由CSDN通过智能技术生成

1.三数之和(双指针)

题目:15.三数之和
思路:题目中要求找到所有不重复和为0的三元组,如果是暴力法,我们需要三重循环。我们先基于三重大循环的框架,来逐步考虑优化。

  • 优化1:如何去重? 一般我们采用哈希表来去重,但这个做法的时间复杂度和空间复杂度要求都很高,因此换一种思考方式:不重复的本质是什么?我们保持暴力法三重循环的大框架不变,在有序数组的情况下只需要保证:
    (1)第二重循环枚举到的元素>=当前第一重循环枚举到的元素
    (2)第三重循环枚举到的元素>=当前第二重循环枚举到的元素
    换言之,保证(a,b,c)这个顺序会被枚举到,而(b,a,c)、(c,b,a)这些不会。

  • 优化2:如何处理列表中的重复元素?对于每一重循环,相邻两次枚举的元素不能相同,否则也会造成重复,因此我们需要将循环跳到下一个不相同的元素,举个例子:
    (1)[0,1,2,2,2,3]->第一个三元组(0,1,2),第二个三元组(0,1,2),第三个三元组(0,1,2)造成重复,我们希望得到(0,1,2)后的下一个三元组是(0,1,3)
    (2)具体优化,体现到代码上就是:

    if i > 0 and nums[i] == nums[i-1]:
        continue
    
  • 如果我们固定了前两重循环枚举到的元素a和b,那么唯一的c满足a+b+c=0。当第二重循环往后枚举一个元素b’时,由于b’>b,那么满足a+b’+c’的c’一定有c’<c,即c’在数组中一定出现在c的左侧。也就是我们可以从小到大枚举b的同时,从大到小枚举c(即双指针),即第二重循环和第三重循环实际上是并列关系。当我们需要枚举数组中的两个元素时,如果我们发现随着第一个元素的递增,第二个元素是递减的,就可以使用双指针方法,将时间复杂度由O(n^2)降到O(n)。

    for i in range(len(nums)-2):
        if i > 0 and nums[i] == nums[i-1]:
            continue
        left = i + 1
        right = len(nums) - 1
        res = []
        while left < right:
            if nums[i] + nums[left] + nums[right] == 0:
                res.append([nums[i], nums[left], nums[right]])
                return res
            elif nums[i] + nums[left] + nums[right] > 0:
                right -= 1
            else:
                left += 1
    
  • 整体优化代码

    def threeSum(nums:List[int]):
        nums.sort()
        res = []
        for i in range(len(nums)):
            if i > 0 and nums[i] == nums[i-1]:
                continue
            k = len(nums) - 1
            for j in range(i+1, len(nums)):
                if j > i + 1 and nums[j] == nums[j-1]:
                    continue
                while j < k and nums[i] + nums[j] + nums[k] > 0:
                    k -= 1
                if j == k:
                    break
                if nums[i] + nums[j] + nums[k] == 0:
                    res.append([nums[i], nums[j], nums[k]])
        return res
    

2.四数之和(双指针)

题目:18.四数之和
思路和三数之和同,只是多了一层循环,注意剪枝操作的坑!!!循环过程仍然遵循以下几点:
(1)每一重循环枚举到的下标必须大于上一重循环枚举到的下标;
(2)同一重循环中,如果当前元素与上一个元素相同,则跳过当前元素。

  • 具体过程:
    (1)使用两重循环分别枚举前两个数a,b,然后在两重循环枚举到的数之后使用双指针枚举后两个数c,d。假设a,b两个数的下标分别为i,j,则左右指针分别指向下标j+1,n-1。每次计算四个数之和,并进行如下操作:
    • 如果和等于target,则将该4个数装进列表中,然后左指针右移直到遇到不同的数,将右指针左移直到遇到不同的数
    • 如果和小于target,左指针右移
    • 如果和大于target,右指针左移
  • 剪枝操作:
    (1)在确定第一个数之后,如果nums[i] + nums[i+1] + nums[i+2] + nums[i+3] > target,说明此时剩下的三个数无论取什么值,四数之和一定大于target,因此退出第一重循环;注意是退出循环,直接break
    (2)在确定第一个数之后,如果nums[i] + nums[n-3] + nums[n-2] + nums[n-1] < target,说明此时剩下的三个数无论取什么值,四数之和一定小于target,因此第一重循环直接进入下一轮,枚举nums[i+1];注意此时的i是太小了,所以退出当前循环,去枚举nums[i+1],因此用continue
    (3)在确定前两个数之后,如果nums[i] + nums[j] + nums[j+1] + nums[j+2] > target,说明此时剩下的两个数无论取什么值,四数之和一定大于 \textit{target}target,因此退出第二重循环;同理,是退出循环,直接break
    (4)在确定前两个数之后,如果nums[i]+nums[j]+nums[n−2]+nums[n−1] < target,说明此时剩下的两个数无论取什么值,四数之和一定小于target,因此第二重循环直接进入下一轮,枚举nums[j+1]。同理,退出当前循环,去枚举nums[i+1],因此用continue
  • 整体优化代码
    def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
        res = list()
        if not nums or len(nums) < 4:
            return res
    
        nums.sort()
        n = len(nums)
        for i in range(n-3):
            if i > 0 and nums[i] == nums[i-1]: 
                continue
            if nums[i] + nums[i+1] + nums[i+2] + nums[i+3] > target: 
                break  # 剪枝操作
            if nums[i] + nums[n-3] + nums[n-2] + nums[n-1] < target: 
                continue  # 剪枝操作
            for j in range(i+1, n-2):
                if j > i+1 and nums[j] == nums[j-1]: 
                    continue
                if nums[i] + nums[j] + nums[j+1] + nums[j+2] > target: 
                    break  # 剪枝操作
                if nums[i] + nums[j] + nums[n-2] + nums[n-1] < target: 
                    continue  # 剪枝操作
                left, right = j+1, n-1
                while left < right:
                    if nums[i] + nums[j] + nums[left] + nums[right] == target:
                        res.append([nums[i], nums[j], nums[left], nums[right]])
                        while left < right and nums[left] == nums[left+1]:
                            left += 1
                        left += 1
                        while left < right and nums[right] == nums[right-1]:
                            right -= 1
                        right -= 1
                    elif nums[i] + nums[j] + nums[left] + nums[right] < target:
                        left += 1
                    else:
                        right -= 1
        return res
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值