3Sums——Leetcode 0015三数之和_枚举类方法

题目描述

Given an integer array nums, return all the triplets [nums[i], nums[j], nums[k]] such that i ! = j i != j i!=j, i ! = k i != k i!=k, and j ! = k j != k j!=k, and n u m s [ i ] + n u m s [ j ] + n u m s [ k ] = = 0 nums[i] + nums[j] + nums[k] == 0 nums[i]+nums[j]+nums[k]==0.

Notice that the solution set must not contain duplicate triplets.

Example 1:

Input: nums = [-1,0,1,2,-1,-4]
Output: [[-1,-1,2],[-1,0,1]]

Example 2:

Input: nums = []
Output: []

Constraints:

0 < = n u m s . l e n g t h < = 3000 0 <= nums.length <= 3000 0<=nums.length<=3000
− 1 0 5 < = n u m s [ i ] < = 1 0 5 -10^5 <= nums[i] <= 10^5 105<=nums[i]<=105

解题思路

直接基于三层循环的思路

无论是算法新手还是老司机,这个方法应该都是极易想到并且理解的。设定三层循环,找到其所有的元素个数为3的子集并完成子集合并即可实现,找到其中和为0的子集即为最后结果。

def threeSum_General(self, nums: List[int]) -> List[List[int]]:
    # 元素数目小于3个,不存在解
    if len(nums) < 3:
        return []
    # 元素个数刚好为3个,如果恰好为0,则为
    if len(nums) == 3:
        if sum(nums) == 0:
            return [nums]
        else:
            return []
    # 元素个数>3个
    result = []
    # 3层循环
    for i in range(0, len(nums)):
        for j in range(i + 1, len(nums)):
            for k in range(j + 1, len(nums)):
                # 判断是否和为零
                if nums[i] + nums[j] + nums[k] == 0:
                    # 满足条件的话对三个元素进行排序,并检查在结果集合中是否已经存在该结果
                    temp = sorted([nums[i], nums[j], nums[k]])
                    # 如果还不存在该解,将其加入最终的解集
                    if temp not in result:
                        result.append(list(temp))
    return result

但是在使用上述方法时,leetcode直接就报了超时的错误。不难理解,即便数组长度范围限制在3000以内,这种 O ( N 3 ) O(N^3) O(N3)的解法的耗时也是极其可观的。为此,只能另寻别路。

先排序,再循环的解决方法

之所以考虑先排序,是因为在上述的做法中我们每一步都要做一次排序的工作,因此不如直接对整个数组做排序,随后采用三层循环的方案。这样子可以直接保证三个数字的顺序,代码如下:

def threeSum_Sort(self, nums: List[int]) -> List[List[int]]:
    # 元素数目小于3个,不存在解
    if len(nums) < 3:
        return []
    # 元素个数刚好为3个,如果恰好为0,则为
    if len(nums) == 3:
        if sum(nums) == 0:
            return [nums]
        else:
            return []
    # 元素个数>3个
    result = []
    # 3层循环
    nums = list(sorted(nums))
    for i in range(0, len(nums)):
        if i == 0 or nums[i] != nums[i - 1]:
            for j in range(i + 1, len(nums)):
                if j == i + 1 or nums[j] != nums[j - 1]:
                    for k in range(j + 1, len(nums)):
                        if k == j + 1 or nums[k] != nums[k - 1]:
                            # 判断是否和为零
                            if nums[i] + nums[j] + nums[k] == 0:
                                # 满足条件的话对三个元素进行排序,并检查在结果集合中是否已经存在该结果
                                temp = [nums[i], nums[j], nums[k]]
                                result.append(list(temp))
    return result

通过分析上述代码不难发现,这种先排序的方法并不会影响时间复杂度,因为三层循环的框架依旧在。那么如何才能摆脱三层循环的限制呢?官方的题解在分析时指出,在排序之后的数组 n u m s nums nums中枚举时,由于取到的三个数字在顺序上是升序的,在选定 a , b a, b a,b之后,有唯一的 c c c满足 a + b + c = 0 a+b+c=0 a+b+c=0这一条件,那么下一次枚举时,对应找到 b ′ , c ′ b', c' b,c,满足上述条件,由于此时 b ′ > b b'>b b>b,那么此时 c ′ < c c'<c c<c,因此内层的两个循环其实可以合并为一个,这正是我们在对数组进行排序后带来的新特性。这样一来,第一层枚举在最外层,内层的两层枚举变为了同一层,时间复杂度自然从 O ( N 3 ) O(N^3) O(N3)变为 O ( N 2 ) O(N^2) O(N2),而排序带来的复杂度为 O ( N L o g N ) O(NLogN) O(NLogN),在取渐进意义时其可以忽略,整体的时间复杂度记为 O ( N 2 ) O(N^2) O(N2)。下面给出代码:

'''
既然上述方案会导致超时,只能另寻方法
基于双指针的方法,主要改进的思路在于,既然三层循环会带来O(N^3)的复杂度,那么必须想办法摒弃。
由于我们需要考虑最终集合中元素不会重复,一种简单的做法就是先对整个数组排序
在枚举过程中,如果前两个数字a, b一旦确定,那么就有唯一的c与之匹配使得a+b+c=0
而当b向右移动枚举到b'时,由于b'>b,那么满足条件的c'<c也是必然的
因此b和c的枚举可以放到同一层循环,b由小到大,c由大到小
'''
def threeSum(self, nums: List[int]) -> List[List[int]]:
    if len(nums) < 3:
        return []
    if len(nums) == 3:
        if sum(nums) == 0:
            return [nums]
        else:
            return []
    result = []
    # first sort the given array with time complexity O(NlogN)
    nums = list(sorted(nums))
    for i in range(0, len(nums)):
        # nums[i] != nums[i - 1]这一条件是为了排除连续两个相同的数字会被单一枚举对象连续用两次,从而导致重复
        # 下面的所有该判断都是同一目的
        if i == 0 or nums[i] != nums[i - 1]:
            # 第三层枚举所用指针初始化,从后往前
            k = len(nums) - 1
            # 第二层枚举
            for j in range(i + 1, len(nums)):
                # 此处判断与上一个判断目的相同
                if j == i + 1 or nums[j] != nums[j - 1]:
                    # 双指针在使用时需要判断两个指针的相对位置,右指针不能越过左指针
                    # 在指针j更新后,满足条件的k对应的索引势必比上次要小
                    while nums[i] + nums[j] + nums[k] > 0 and k > j:
                        k -= 1
                    # 如果k == j,此时会导致三个数中出现重复,不满足集合的特性,不能作为结果
                    if nums[i] + nums[j] + nums[k] == 0 and k != j:
                        result.append([nums[i], nums[j], nums[k]])
    return result

以上是三数之和问题使用枚举方法求解的基本步骤,大家有新的方法的话可以在评论区交流(上述内容主要来自Leetcode官解,这里只是为了自己记录一下刷题过程中的心得~)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值