15. 3Sum (Medium)

Description:

Given an array nums of n integers, are there elements a, b, c in nums such that a + b + c = 0? Find all unique triplets in the array which gives the sum of zero.

Note:

The solution set must not contain duplicate triplets.

Example:

Given array nums = [-1, 0, 1, 2, -1, -4],

A solution set is:
[
[-1, 0, 1],
[-1, -1, 2]
]


Analysis:

因为很久以前看过这道题的解法,所以首先想到了三指针,但是并不顺利。

一开始的方法是:(wrong)

        l = 0
        r = len(nums)-1
        j = r-1

规则是:如果求和小于0,++left;如果大于零,--j 或者 --right且j=right-1,取决于j不能等于left;如果求和等于0,那么result.append(),并且--right且j=right-1。大循环满足条件是 left

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        if len(nums) < 3:
            return []
        nums.sort()
        l = 0
        r = len(nums)-1
        j = r-1
        result = []
        while (l < r-1):
            cache = nums[l]+nums[j]+nums[r]
            if  cache < 0:
                l += 1
            elif cache > 0 and j > l+1:
                j -= 1
            elif cache > 0 and j <= l+1:
                r -= 1
                j = r-1
            else:
                result.append([nums[l],nums[j],nums[r]])
                r -= 1
                j = r-1
        return result

存在的问题是,不能剔除重复,就算添加一行代码去重复,也还是会出现莫名其妙的漏掉结果,以及多结果。

之后的方法:O(n^3),TLE

全部从左往右,i,j,k = 0,1,2

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        if len(nums) < 3:
            return []
        nums.sort()
        result = []
        i,j,k = 0,1,2
        while (i <= len(nums)-3):
            # print('\n',i,j,k)
            cache = nums[i] + nums[j] + nums[k]
            # print([nums[i],nums[j],nums[k]],cache)
            if cache < 0:
                k += 1
            else:
                if cache == 0 and [nums[i],nums[j],nums[k]] not in result:
                    result.append([nums[i],nums[j],nums[k]])
                if k <= j+2:
                    i += 1
                    j = I+1
                    k = I+2
                else:                    
                    j += 1
                    k = j+1
            if k >= len(nums):
                j += 1
                k = j+1
            if j >= len(nums) -1:
                i += 1
                j = I+1
                k = I+2
        return result

一个可行的解法:O(n^2)

初始化:left = 0,中间指针j = 1,right=last index
每次大循环,left只要不超过len-3就行了,循环结尾++left。循环内部另一个循环是,中间指针j和right互相靠拢。
注意:每次移动任一指针,需要判断是否重复来加速,但引入的新麻烦是要注意index不要超出范围。

class Solution:
    def threeSum(self, nums):
        if len(nums) < 3:
            return []
        nums.sort()
        result = []
        
        l,j,r = 0,1,len(nums)-1
        while (l < r-1):
            while (j < r):
                cache_sum = nums[l]+nums[j]+nums[r]
                cache_triplet = (nums[l],nums[j],nums[r])
                if cache_sum < 0: # j太小
                    while(nums[j] == cache_triplet[1] and j < r):
                        j += 1
                elif cache_sum > 0: # r太大
                    while(nums[r] == cache_triplet[2] and j < r):
                        r -= 1
                else: # 正好合适
                    result.append(cache_triplet) # 已经在别的地方控制不会有重复了,可以放心append
                    if j >= r-2: # 没有下次机会了。如果中间数字存在重复或者不重复都不可能有新的结果。
                        break
                    while(nums[j] == cache_triplet[1] and j < r): # 只要j

Performance:

  • Runtime: 1728 ms, faster than 16.14% of Python3 online submissions for 3Sum.
  • Memory Usage: 16.6 MB, less than 90.14% of Python3 online submissions for 3Sum.


    不知道为什么结果很差,试了一些别人的Python解法,也基本上在1700ms-1900ms之间,或者甚至TLE在一个特别长的case

optimize the previous version: inspired by the following cpp solution

class Solution:
    def threeSum(self, nums):
        if len(nums) < 3:
            return []
        nums.sort()
        result = []
        l,j,r = 0,1,len(nums)-1
        while (l < r-1):
            if nums[l] > 0:
                break
            target = -nums[l]
            while (j < r):
                cache_sum = nums[j]+nums[r]
                if cache_sum < target: # j太小
                    j += 1
                elif cache_sum > target: # r太大
                    r -= 1
                else: # 正好合适
                    result.append((nums[l],nums[j],nums[r])) # 已经在别的地方控制不会有重复了,可以放心append
                    while(j < r and nums[j] == nums[j+1]): # 只要j

Performance:

  • Runtime: 624 ms, faster than 94.01% of Python3 online submissions for 3Sum.
  • Memory Usage: 16.5 MB, less than 95.01% of Python3 online submissions for 3Sum.
  • 提升速度的方法:1,去掉nums[0]>0的情况(ps,去掉nums[-1]<0效果不明显),2,用中间变量来替代反复的nums[index],尤其是长序列效果明显,3,去掉一些对速度提升不大的判断,比如此处查重在测试集里反而变慢:
                if cache_sum < target: # j太小
                    j += 1
                elif cache_sum > target: # r太大
                    r -= 1

A great CPP solution:

source:https://github.com/MisterBooo/LeetCodeAnimation/blob/master/notes/LeetCode%E7%AC%AC15%E5%8F%B7%E9%97%AE%E9%A2%98%EF%BC%9A%E4%B8%89%E6%95%B0%E4%B9%8B%E5%92%8C.md

class Solution {
public:
    vector> threeSum(vector& nums) {
        vector> res;
        sort(nums.begin(), nums.end());
        if (nums.empty() || nums.back() < 0 || nums.front() > 0) return {};
        for (int k = 0; k < nums.size(); ++k) {
            if (nums[k] > 0) break;
            if (k > 0 && nums[k] == nums[k - 1]) continue;
            int target = 0 - nums[k];
            int i = k + 1, j = nums.size() - 1;
            while (i < j) {
                if (nums[i] + nums[j] == target) {
                    res.push_back({nums[k], nums[i], nums[j]});
                    while (i < j && nums[i] == nums[i + 1]) ++i;
                    while (i < j && nums[j] == nums[j - 1]) --j;
                    ++i; --j;
                } else if (nums[i] + nums[j] < target) ++i;
                else --j;
            }
        }
        return res;
    }
};

Performance:

Runtime: 88 ms, faster than 99.50% of C++ online submissions for 3Sum.
Memory Usage: 14.7 MB, less than 75.16% of C++ online submissions for 3Sum.

Transform code form cpp to python:

class Solution:
    def threeSum(self, nums):
        if len(nums) < 3:
            return []
        
        nums.sort()
        if nums[0] > 0 or nums[-1] < 0:
            return []
        
        result = []
        for k in range(len(nums)):
            if nums[k] >0:
                break
            if (k>0 and nums[k] == nums[k-1]):
                continue
            target = 0 - nums[k]
            i = k+1
            j = len(nums) -1
            while(i < j):
                if (nums[i]+nums[j] == target):
                    result.append([nums[k],nums[i],nums[j]])
                    while (i < j and nums[i] == nums[i+1]):
                        i += 1
                    while (i < j and nums[j] == nums[j-1]):
                        j -= 1
                    i += 1
                    j -= 1
                elif nums[i]+nums[j] < target:
                    i += 1
                else:
                    j -= 1

        return result

Performance:

Runtime: 820 ms, faster than 77.77% of Python3 online submissions for 3Sum.
Memory Usage: 16.7 MB, less than 76.91% of Python3 online submissions for 3Sum.


Best implementation in Leetcode:

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        ans = []
        numcounts = self.count(nums)
        nums = sorted(numcounts)
        for i, num in enumerate(nums):
            if numcounts[num] >= 2:
                if num == 0:
                    if numcounts[num] >= 3:
                        ans.append([0, 0, 0])
                else:
                    if (-2 * num) in nums:
                        ans.append([num, num, -2*num])
            if num < 0:
                ans = self.twosum(ans, nums, numcounts, num, i)

        return ans

    def twosum(self, ans: List[List[int]], nums: List[int], numcounts, num: int, i: int):
        twosum = -num
        left = bisect.bisect_left(nums, (twosum-nums[-1]), i+1)
        right = bisect.bisect_right(nums, (twosum//2), left)

        for num2 in nums[left:right]:
            num3 = twosum - num2
            if num3 in numcounts and num3 != num2:
                ans.append([num, num2, num3])

        return ans

    def count(self, nums: List[int]):
        count = {}
        for num in nums:
            if num in count:
                count[num] += 1
            else:
                count[num] = 1
        return count

Performance:

  • Runtime: 228 ms, faster than 99.99% of Python3 online submissions for 3Sum.
  • Memory Usage: 17.4 MB, less than 21.66% of Python3 online submissions for 3Sum.

你可能感兴趣的:(15. 3Sum (Medium))