2021/5/23 刷题笔记三数之和与双指针法

Posted 黑黑白白君

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2021/5/23 刷题笔记三数之和与双指针法相关的知识,希望对你有一定的参考价值。



三数之和

【题目】

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0?请你找出所有和为 0 且不重复的三元组。

  • 注意:答案中不可以包含重复的三元组。

示例 1:

  • 输入:nums = [-1,0,1,2,-1,-4]
  • 输出:[[-1,-1,2],[-1,0,1]]

示例 2:

  • 输入:nums = []
  • 输出:[]

示例 3:

  • 输入:nums = [0]
  • 输出:[]

提示:

  • 0 <= nums.length <= 3000
  • -105 <= nums[i] <= 105

来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/3sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

【我的方法】(超时。。。)

1、给数组排序。
2、使用回溯法,注意三元组不能重复,所以在添加到res前还需要判断是否已存在。

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        res=[]
        if len(nums)<3:
            return []
        def backtrack(sub,temp,tol):
            if len(temp)==3:
                if tol==0:
                    temp.sort()
                    if temp not in res:
                        res.append(temp)
                return
            elif len(temp)<3:
                n=len(sub)
                for i in range(n):
                    if tol+sub[i]<=0:
                        backtrack(sub[i+1:],temp+[sub[i]],tol+sub[i])
            else:
                return
        nums.sort()
        backtrack(nums,[],0)
        return res

【其他方法】

  • 排序+双指针

1、不重复:

  • 实际上就是枚举的三元组 (a, b, c)满足 a≤b≤c,保证了只有 (a, b, c)这个顺序会被枚举到,而 (b, a, c)、(c, b, a)等等不会,这样就减少了重复。
    • 可以通过将数组中的元素从小到大进行排序,然后使用普通的三重循环。
  • 同时对于每一重循环而言,相邻两次枚举的元素不能相同,否则也会造成重复。
  • 此时时间复杂度为O(N^3)

2、循环优化:

  • 如果固定了前两重循环枚举到的元素 a 和 b,那么只有唯一的 c 满足 a+b+c=0。所以可以从小到大枚举 b,同时从大到小枚举 c,即第二重循环和第三重循环实际上是并列的关系。
    • 使用双指针法,即左指针向右移动一个位置,而右指针会向左移动若干个位置。
  • 此时时间复杂度降为O(N^2)

借鉴该思路写出的代码:

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        if len(nums)<3:
            return []
        res=[]
        nums.sort()
        i=0
        n=len(nums)
        while(i<n-2):
            j=i+1
            k=n-1
            while(j<k):
                if (nums[i]+nums[j]+nums[k])==0:
                    res.append([nums[i],nums[j],nums[k]])
                    now=nums[j]
                    j+=1
                    k-=1
                    while(nums[j]==now and j<k):  # 避免枚举和之前一样的数
                        j+=1
                elif (nums[i]+nums[j]+nums[k])<0:
                    j+=1
                else:
                    k-=1
            now=nums[i]
            i+=1
            while(nums[i]==now and i<n-2):  # 避免枚举和之前一样的数
                i+=1
        return res
# 执行用时:1544 ms, 在所有 Python3 提交中击败了17.88%的用户
# 内存消耗:17.3 MB, 在所有 Python3 提交中击败了93.48%的用户

参考代码:

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        n = len(nums)
        nums.sort()
        ans = list()
        
        # 枚举 a
        for first in range(n):
            # 需要和上一次枚举的数不相同
            if first > 0 and nums[first] == nums[first - 1]:
                continue
            # c 对应的指针初始指向数组的最右端
            third = n - 1
            target = -nums[first]
            # 枚举 b
            for second in range(first + 1, n):
                # 需要和上一次枚举的数不相同
                if second > first + 1 and nums[second] == nums[second - 1]:
                    continue
                # 需要保证 b 的指针在 c 的指针的左侧
                while second < third and nums[second] + nums[third] > target:
                    third -= 1
                # 如果指针重合,随着 b 后续的增加
                # 就不会有满足 a+b+c=0 并且 b<c 的 c 了,可以退出循环
                if second == third:
                    break
                if nums[second] + nums[third] == target:
                    ans.append([nums[first], nums[second], nums[third]])
        
        return ans
# 执行用时:844 ms, 在所有 Python3 提交中击败了58.27%的用户
# 内存消耗:17.5 MB, 在所有 Python3 提交中击败了70.77%的用户


双指针

1、什么是双指针(对撞指针、快慢指针)

双指针,指的是在遍历对象的过程中,不是普通的使用单个指针进行访问,而是使用两个相同方向(快慢指针)或者相反方向(对撞指针)的指针进行扫描,从而达到相应的目的。

  • 什么时候优先想到用双指针来解决问题?

    • 当遇到有序数组时:双指针法充分使用了数组有序这一特征,从而在某些情况下能够简化一些运算。例如:
      • 给定一个有序递增数组,在数组中找到满足条件的两个数,使得这两个数的和为某一给定的值。对于这种问题,常见的算法思路不外乎遍历,回溯,但这里,双指针遍历法是一个很有效的方法。
      • hoare的双向扫描快速划分法
      • 单链表的中间元素。

2、对撞指针用法

对撞指针是指在有序数组中,将指向最左侧的索引定义为左指针(left),最右侧的定义为右指针(right),然后从两头向中间进行数组遍历

3、快慢指针用法

两个指针从同一侧开始遍历数组,将这两个指针分别定义为快指针(fast)和慢指针(slow),两个指针以不同的策略移动,直到两个指针的值相等(或其他特殊条件)为止,如fast每次增长两个,slow每次增长一个。



【部分内容参考自】

  • 三数之和:https://leetcode-cn.com/problems/3sum/solution/san-shu-zhi-he-by-leetcode-solution/
  • 算法一招鲜——双指针问题:https://zhuanlan.zhihu.com/p/71643340
  • 【算法总结–数组相关】双指针法的常见应用。:https://blog.csdn.net/ohmygirl/article/details/7850068

以上是关于2021/5/23 刷题笔记三数之和与双指针法的主要内容,如果未能解决你的问题,请参考以下文章

三数之和,双指针法,细节很多

双指针法及题目

双指针法及题目

双指针法及题目

leetcode 15. 三数之和

(双指针)Java 求解三数之和