JAVA:三数之和

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

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

例如, 给定数组 nums = [-1, 0, 1, 2, -1, -4],

满足要求的三元组集合为:
[
  [-1, 0, 1],
  [-1, -1, 2]
]

解题思路

我们首先想到的解法是通过三重循环,于是我就写出了如下代码:

class Solution:
    def threeSum(self, nums):
        """
        :type nums: List[int]
        :rtype: List[List[int]]
        """
        result = []
        for i, a in enumerate(nums):
            for j, b in enumerate(nums[i + 1:]):
                for _, c in enumerate(nums[j + i + 2:]):
                    if a + b + c == 0:
                        result.append([a, b, c])
        return result

但是上面这个代码是有问题的,因为我们没有考虑结果重复的问题。接着我们想到可以通过collections.Counter记录所有数字出现的次数,如果前后有相同的话,我们就不添加到result中去。于是就有了下面的写法

class Solution:
    def threeSum(self, nums):
        """
        :type nums: List[int]
        :rtype: List[List[int]]
        """
        from collections import Counter
        k = []
        result = []
        for i, a in enumerate(nums):
            for j, b in enumerate(nums[i + 1:]):
                for _, c in enumerate(nums[j + i + 2:]):
                    if a + b + c == 0 and Counter([a, b, c]) not in k:
                        k.append(Counter([a, b, c]))
        for i in k:
            result.append(list(i.elements()))
        return result

但是这种写法的缺点很明显,算法的时间复杂度是O(n^3)这个级别的。我们能不能优化到O(n^2)这个级别呢?我们可以参考Leetcode 1:两数之和(最详细解决方案!!!)文中的方法,通过一个hash表来记录nums中所有元素出现的次数。

class Solution:
    def threeSum(self, nums):
        """
        :type nums: List[int]
        :rtype: List[List[int]]
        """
        nums_hash = {}
        result = list()
        for num in nums:
            nums_hash[num] = nums_hash.get(num, 0) + 1
        if 0 in nums_hash and nums_hash[0] >= 3:
            result.append([0, 0, 0])

        nums = sorted(list(nums_hash.keys()))

        for i, num in enumerate(nums):
            for j in nums[i+1:]:
                if num*2 + j == 0 and nums_hash[num] >= 2:
                    result.append([num, num, j])
                if j*2 + num == 0 and nums_hash[j] >= 2:
                    result.append([j, j, num])

                dif = 0 - num - j
                if dif > j and dif in nums_hash:
                    result.append([num, j, dif])

        return result

当然这个算法还有优化的空间,我们知道三个数和为0,那么在三个数不全为0的情况下,必然有一个正数和一个负数,那么我们可以通过两个list去存取nums中含有不重复元素的正数和负数。那样我们就不用O(n^2)(n=len(nums))的时间,而只需要O(n*m)(n+m=len(nums))的时间复杂度。

另外我们还知道一个条件,对于a,b,c三个数,如果a是正数,b是负数,那么-c一定比b小,或者比a大。

例如:

a = 1
b = -2 
c = 1    -c = -1

a = 3
b = -2
c = -1   -c = 1
同时也很好证明
-c = (a + b)即-c - a = b a>=0 -> -c < b
-c = (a + b)即-c - b = a b<0  -> -c > a

所以我们可以这样去解这个问题。

class Solution:
    def threeSum(self, nums):
        """
        :type nums: List[int]
        :rtype: List[List[int]]
        """
        nums_hash = {}
        result = list()
        for num in nums:
            nums_hash[num] = nums_hash.get(num, 0) + 1
        if 0 in nums_hash and nums_hash[0] >= 3:
            result.append([0, 0, 0])

        neg = list(filter(lambda x: x < 0, nums_hash))
        pos = list(filter(lambda x: x>= 0, nums_hash))

        for i in neg:
            for j in pos:
                dif = 0 - i - j
                if dif in nums_hash:
                    if dif in (i, j) and nums_hash[dif] >= 2:
                        result.append([i, j, dif])
                    if dif < i or dif > j:
                        result.append([i, j, dif])

        return result

此处应有掌声,非常好的解法是不是O(∩_∩)O

另外这个问题我们也可以使用Leetcode 167:两数之和 II - 输入有序数组(最详细解决方案!!!)这篇文章中提到的对撞指针的思路。我们首先要将nums排序。

-4 -1 -1  0  1  2
i   l           r
l = i+1

我们实际上只要考虑nums[i] <= 0的部分,因为当nums[i] > 0时,必然会造成nums[i], nums[l], nums[r]全部>0,这显然不对。当i > 0时,我们要考虑nums[i - 1] == nums[i],如果成立,我们要跳出本次循环,执行++i,直到不成立为止。

所以我们就有了如下的做法

class Solution:
    def threeSum(self, nums):
        """
        :type nums: List[int]
        :rtype: List[List[int]]
        """
        result = list()
        nums_len = len(nums)
        if nums_len < 3:
            return result
        l, r, dif = 0, 0, 0
        nums.sort()
        for i in range(nums_len - 2):
            if nums[i] > 0: 
                break
            if i > 0 and nums[i - 1] == nums[i]:
                continue

            l = i + 1
            r = nums_len - 1
            dif = -nums[i]
            while l < r:
                if nums[l] + nums[r] == dif:
                    result.append([nums[l], nums[r], nums[i]])
                    while l < r and nums[l] == nums[l + 1]:
                        l += 1
                    while l < r and nums[r] == nums[r - 1]:
                        r -= 1
                    l += 1
                    r -= 1
                elif nums[l] + nums[r] < dif:
                    l += 1
                else:
                    r -= 1

        return result
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值