LeetCode刷题笔记-15.三数之和(3sum)

问题描述

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

说明

  • 答案中不可以包含重复的三元组
  • 0 <= nums.length <= 3000
  • -105 <= nums[i] <= 105


示例 1

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

示例 2:

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

示例 3:

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



题解

1.暴力解法

算法解析

使用3重循环,一次循环固定一个数,通过3次循环找到解:

  • 第一层循环固定下标为i的数,i递增直到n-1
  • 第二层循环固定下标为j = i+1的数,j递增直到n-1
  • 第三层循环固定下标为k = j+1的数,k递增直到n-1

同时还可以通过将数组排序,避免最后的解集中出现组合一样但是顺序不同的解.

复杂度分析
  • 时间复杂度: \(O(N^3)\), 使用3重循环,其时间复杂度为\(O(N^3)\)
  • 空间复杂度: \(O(1)\), 只需要固定的额外空间

代码实现
  • Python版
class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        n = len(nums)
        if n < 3:
            return []

        res = set() # 防止解重复
        nums.sort() # 防止出现值次序不同但值组合相同的解
        for i in range(n):
            for j in range(i+1, n):
                for k in range(j+1, n):
                    if nums[i] + nums[j] + nums[k] == 0:
                        res.add(f"{nums[i]}_{nums[j]}_{nums[k]}")
                        # f字符串前缀表达式为Python3.6及之后的特性
        return [list(map(int, s.split('_'))) for s in res]


2.双指针解法

算法解析

该解法同上面暴力解法类似,同样的是固定一个数nums[i]然后求剩余两个数之和.只不过在第二层循环和第三层循环时,利用数组已经排序好的特点,采用双指针代替第二层循环和第三层循环,也就是说通过一个左指针left从前往后移动和一个右指针right从后往前移动找到满足题解条件的剩余的两个数.同样的需要先对数组排序.具体细节如下:

  • nums[len-1] < 0时,由于数组为递增数组,则在最后一个元素都小于0情况下不可能再有解,直接返回结果
  • nums[i] > 0时,由于数组为递增数组,则i之后的不可能有三数之和小于0,直接返回结果
  • nums[i] == nums[i-1](i > 0)时,即有重复的元素时,跳过重复元素.
  • 当令left = i+1right = n-1,当left < right时移动指针规则如下:
    • nums[left] + nums[right] + nums[i] > 0时,则nums[right]太大,向前移动right指针
    • nums[left] + nums[right] + nums[i] < 0时,则nums[right]太小,向后移动left指针
    • nums[left] + nums[right] + nums[i] == 0时,则找到一个解,记录该解,并将左指针向后,右
      指针向后移动寻找下一解,同时还应该跳过nums[left] == nums[left-1]或者nums[right] == nums[right+1]这样的重复元素的情况

本题难点在于如何避免重复解,通过排序和双指针法便可轻松的解决这一问题,这一解法还可推广到nSum问题(n>=3),可通过递归解决.

参考文章及资料

LeetCode精选题解
LeetCode精选题解

复杂度分析
  • 时间复杂度: \(O(N^2)\),一般排序算法时间复杂度为\(O(NlogN)\),而双指针遍历和和最外层循环的时间复杂度为\(O(N^2)\)
  • 空间复杂度: \(O(logN)\)或者\(O(1)\), 一般排序算法空间复杂度为\(O(logN)\)而对于本题核心算法来说其空间复杂度为\(O(1)\)

代码实现
  • Java版
class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        int len = nums.length;
        List<List<Integer>> res = new ArrayList<List<Integer>>();
        
        if (len < 3) return res; // 特殊情况
        
        Arrays.sort(nums); // 排序便于去重
        // 特性情况,数组元素递增,当nums[len-1] < 0时,不可能有解
        if (nums[len-1] < 0) return res; 
        for (int i = 0; i < len; ++i) { // 固定一个数, 寻找剩余两个数
            // 剪枝,数组元素递增,当nums[i] > 0时,nums[i]及之后不可能再有解
            if (nums[i] > 0) return res; 
            // 若存在和之前相同数则应该跳过,防止重复解
            if (i > 0 && nums[i] == nums[i-1]) continue; 

            int left = i+1; 
            int right = len-1;
            while (left < right) {
                int tmpSum = nums[left] + nums[right] + nums[i];
                if (tmpSum > 0) { 
                    --right; // 此时nums[right]值太大,应向前移动
                } else if (tmpSum < 0) {
                    ++left; // 此时nums[left]值太小,应向后移动
                } else { // 此时三数之和等于0,记录该解
                    List<Integer> tmp = new ArrayList<>(3);
                    tmp.add(nums[i]);
                    tmp.add(nums[left++]); // 向后移动,寻找下一解
                    tmp.add(nums[right--]); // 向前移动,寻找下一解
                    res.add(tmp);
                    // 跳过重复的解
                    while (left < right && nums[left] == nums[left-1]) ++left;
                    while (left < right && nums[right] == nums[right+1]) --right;
                }
            }
        } // end for
        return res;
    }
}
  • Python版
class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        n = len(nums)
        res = list()
        if (n < 3):
            return res

        nums.sort()
        if nums[n-1] < 0:
            return res
        for i in range(n):
            if nums[i] > 0:
                return res
            if i > 0 and nums[i] == nums[i-1]:
                continue
            
            left, right = i+1, n-1
            while left < right:
                if nums[i] + nums[left] + nums[right] < 0:
                    left += 1
                elif nums[i] + nums[left] + nums[right] > 0:
                    right -= 1
                else:
                    res.append([nums[i], 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
        return res



解题误区及心得总结

误区
  • 对于双指针的理解不深入
  • 对于排序在算法中的应用有欠缺
心得总结
  • 双指针在序列中,特别时元素有特性的序列中求解很合适,甚至可以达到降维的目的
  • 排序与去重有着很强的联系,通过先排序可以快速的去重.
posted @ 2021-01-22 19:18  一生至为你  阅读(245)  评论(0)    收藏  举报