【每日一题】【LeetCode】【第一天】【Python】三数之和

三数之和的解决之路= =

题干表述

在这里插入图片描述

测试案列(部分)

在这里插入图片描述
在这里插入图片描述

第一次思路

这种其实是最暴力的,也是我脑海里第一个想到的最简单的方法了。
思路就是三个循环,一个循环去一个数,然后当三个下标不同,且对应的三个数相加为0时就append追加进列表

# -*- encoding: utf-8 -*-
class Solution(object):
    def threeSum(self, nums):
        """
        :type nums: List[int]
        :rtype: List[List[int]]
        """
        res_list = []
        index = range(len(nums))
        for i in index:
            for j in index:
                for k in index:
                    if i!=j and j!=k and i!=k and nums[i]+nums[j]+nums[k]==0:
                        res_list.append([nums[i], nums[j], nums[k]])
        return res_list

在这里插入图片描述

这种肯定会有错的,运行之后会发现,重复了很多排列顺序不同的三个同一元素。所以我们暂不考虑减少时间复杂度的情况,先考虑怎么解决去重的问题。

第二次

我们对于第一次代码中的三个!=进行优化,其实用小于号更好,既能避免下标重复,又能避免去重问题。

# -*- encoding: utf-8 -*-
class Solution(object):
    def threeSum(self, nums):
        """
        :type nums: List[int]
        :rtype: List[List[int]]
        """
        res_list = []
        index = range(len(nums))
        for i in index:
            for j in index:
                for k in index:
                    if i<j and j<k and nums[i]+nums[j]+nums[k]==0:
                        res_list.append([nums[i], nums[j], nums[k]])
        
        return res_list

但结果还是不对,小于号虽然能解决一点去重问题,但是也就只能解决一点点。。
在这里插入图片描述

顺序不同的三个同一元素序列问题解决了,但是存在三个元素下标不同,但三个元素与另外三个元素重复的情况。
比如上图,两个[-1, 0, 1]其实是下标为0的-1和下标为4的-1造成的重复。

第三次

if判断的条件就得更严格,res_list里面有的列表,就不往里面添加。

# -*- encoding: utf-8 -*-
class Solution(object):
    def threeSum(self, nums):
        """
        :type nums: List[int]
        :rtype: List[List[int]]
        """
        res_list = []
        index = range(len(nums))
        for i in index:
            for j in index:
                for k in index:
                    if i<j and j<k and nums[i]+nums[j]+nums[k]==0:
                        if ([nums[i], nums[j], nums[k]] not in res_list) and ([nums[j], nums[i], nums[k]] not in res_list) and ([nums[j], nums[k], nums[i]] not in res_list) and ([nums[i], nums[k], nums[j]] not in res_list) and ([nums[k], nums[j], nums[i]] not in res_list) and ([nums[k], nums[i], nums[j]] not in res_list):
                            res_list.append([nums[i], nums[j], nums[k]])

        return res_list

这个测试是正确的,但是也是暴力解法,看起来太蠢了,哈哈哈,这么长的枚举判断我自己都受不了

在这里插入图片描述

但是提交会发现,超出时间限制。
执行到超时的案例是数值比较极端一个数据。

在这里插入图片描述

第四次

那肯定是那个枚举的if判断需要改善,因为想不到还有什么办法,翻了翻评论区,发现有人的代码第一步是将输入序列排序,自己立马想到,可能有序序列就不需要这种枚举,可能只需要一个[nums[i], nums[j], nums[k]] not in res_list即可

# -*- encoding: utf-8 -*-
class Solution(object):
    def threeSum(self, nums):
        """
        :type nums: List[int]
        :rtype: List[List[int]]
        """
        nums.sort()
        res_list = []
        index = range(len(nums))
        for i in index:
            for j in index:
                for k in index:
                    if i<j and j<k and nums[i]+nums[j]+nums[k]==0:
                        if [nums[i], nums[j], nums[k]] not in res_list:
                            res_list.append([nums[i], nums[j], nums[k]])

        return res_list

排序之后,可以不用那么多if条件了,测试没问题,但提交还是超时了额

在这里插入图片描述

第五次

我想可能是三次循环太多了,这个if倒是可以往后放一放。
翻了翻评论区,注意到了一个哈希法

在这里插入图片描述

但后半部分提到的vector我只知道是向量,之前自己学Spark和数据结构的时候见过,但也只是知道它是向量而已,多的就不知道了。
但是前半部分的思路可以借鉴,通过0-a-b算出c,可以少一个循环。

# -*- encoding: utf-8 -*-
class Solution(object):
    def threeSum(self, nums):
        """
        :type nums: List[int]
        :rtype: List[List[int]]
        """
        nums.sort()
        res_list = []
        n = len(nums)
        for i in range(n):
            for j in range(i+1, n):
                res = 0-nums[i]-nums[j]
                if res in nums[j+1:]:
                    if [nums[i], nums[j], res] not in res_list:
                        res_list.append([nums[i], nums[j], res])

        return res_list

改良了一下,改成了双循环,但是还是超时了??可能是range()比较耗时?不理解。。

在这里插入图片描述

第六次

参考了评论区的双指针法

在这里插入图片描述

有点像快排的每趟思路,首先要确立好基本思路,就是先排序为有序序列,然后每趟for循环从前到后选一个基准,然后对这个基准后面的序列进行两个元素的选取,使三个元素相加=0

然后这个选取两个元素的过程是,先取两头left(i+1的位置)和right(n-1的位置),然后sum大了,right-1sum小了left+1(因为是增序序列,所以这种逻辑是可以的,这里可能不常了解算法的人会烧CPU,可以反复理解一下,最好拿张纸写个序列试一试)。

# -*- encoding: utf-8 -*-
class Solution(object):
    def threeSum(self, nums):
        """
        :type nums: List[int]
        :rtype: List[List[int]]
        """
        nums.sort()
        res_list = []
        n = len(nums)
        for i in range(n):
            left = i+1
            right = n-1
            while True:
                if left >= right:
                    break
                if nums[i]+nums[left]+nums[right] > 0:
                    right -= 1
                    continue
                elif nums[i]+nums[left]+nums[right] < 0:
                    left += 1
                    continue
                else:
                    if [nums[i], nums[left], nums[right]] not in res_list:
                        res_list.append([nums[i], nums[left], nums[right]])
                        break

        return res_list

按双指针思路写的代码,可惜,最后还是超时了。但最后的输入数据变了,是[0,0,0,0]没过。

在这里插入图片描述

第七次

对比了一下别人写的代码,修改了第七版的代码,发现了第六版还有些许错误,都在下面代码的注释里。

# -*- encoding: utf-8 -*-
class Solution(object):
    def threeSum(self, nums):
        """
        :type nums: List[int]
        :rtype: List[List[int]]
        """
        nums.sort()
        res_list = []
        n = len(nums)
        for i in range(n):
            left = i+1
            right = n-1
            # 增序序列第一个元素大于0,三数之和就不可能为0
            if nums[i] > 0:
                break
            # 没搞懂,暂先不加试试
            # if i >= 1 and nums[i] == nums[i-1]:
            #     continue
            while left < right:     # left<right这一限制条件就可以直接写到这来
                s = nums[i]+nums[left]+nums[right]      # 求和结果用到了两次,就可以先存储一个变量
                if s > 0:
                    right -= 1
                    # continue  加了等于没加,不需要continue
                elif s < 0:
                    left += 1
                    # continue  加了等于没加,不需要continue
                else:
                    if [nums[i], nums[left], nums[right]] not in res_list:
                        res_list.append([nums[i], nums[left], nums[right]])
                        # 这里不应该break,而是继续找下去,不然会漏
                        # break
                        left += 1
                        right -= 1

        return res_list

当然,代码里面还是有我的坚持。。。。原代码有两处没理解什么意思所以没加,先试了试我这一拼凑改良版。

在这里插入图片描述
这次连第一个测试案例都过不了,应该是死循环了。

第八次(抄的)

class Solution:
    def threeSum(self, nums):
        ans = []
        n = len(nums)
        nums.sort()
        for i in range(n):
            left = i + 1
            right = n - 1
            if nums[i] > 0:
                break
            if i >= 1 and nums[i] == nums[i - 1]:
                continue
            while left < right:
                total = nums[i] + nums[left] + nums[right]
                if total > 0:
                    right -= 1
                elif total < 0:
                    left += 1
                else:
                    ans.append([nums[i], nums[left], nums[right]])
                    while left != right and nums[left] == nums[left + 1]: left += 1
                    while left != right and nums[right] == nums[right - 1]: right -= 1
                    left += 1
                    right -= 1
        return ans

和自己一开始到最后的思路不同的是以下几个点:

  1. 能减少循环次数的就要减少。
    比如循环一开始的这个语句
if nums[i] > 0:
    break

升序序列第一个元素就比0大,那三个数的和就肯定>=0了,就不需要进行后续的while循环了。

  1. 去重的方法太费时间了,nums很长的时候if [nums[i], nums[left], nums[right]] not in res_list也得要O(n),这个点最关键,体现的地方有两处代码。
# 第一处(循环一开始处的语句)
if i >= 1 and nums[i] == nums[i - 1]:
    continue

# 第二处(while循环中else分支的语句)
while left != right and nums[left] == nums[left + 1]: left += 1
while left != right and nums[right] == nums[right - 1]: right -= 1

第一处是如何巧妙去重的呢,自己单看第一遍的时候没有理解,在手动模拟了一遍流程渐渐理解了。

整体流程(只手写了三轮循环):

在这里插入图片描述
过程描述:

序列[-1, 0, 1, 2, -1, -4]进入函数,首先会被升序排序变为[-4, -1, -1, 0, 1, 2],然后开始循环。

【第一轮for循环】i指向下标为0的地方(也就是-4),按规矩left就指向下标为0+1=1的地方(也就是-1),right指向下标为6-1=5的地方(也就是2
此时total=-4+(-1)+2=-3<0,所以进行left+1操作。
然后可以看出,经过四次left+1操作,因left=right,导致while循环结束,第一轮for循环结束。

此轮循环中,left指针移动4次、right指针移动0次、比较次数应为4次。

【第二轮for循环】i指向下标为1的地方(也就是-1),按规矩left就指向下标为1+1=2的地方(也就是-1),right指向下标为6-1=5的地方(也就是2
此时total=-1+(-1)+2=0,所以进行append()操作(添加了[-1, -1, 2]),向结果列表res中追加此时的三元组。但是,while循环并没有结束!(之前自己算法设计的思路就break结束了,会导致漏掉的情况)
此时同时进行left+1right-1操作,然后继续寻找是否存在更多的合法三元组。然后又找到了[-1, 0, 1],继续append(),然后继续对leftright进行操作,导致while循环条件不成立,while循环结束,for循环结束。

此轮循环中,left指针移动2次,right指针移动了2次,比较次数应为2次。

【第三轮for循环】i指向下标为2的地方(也就是-1),此时判断nums[i] == nums[i - 1],就会跳过while循环,直接开始第四轮for循环。
但是,为什么这样就跳过了呢?这样就能避免重复?先拿这个栗子说明。
下标为1的-1可以在下标从2到5的4个元素中有两组数据符合要求,那么下标为2的-1在下标从3到5的3个元素中,只有两个可能,一个是有符合要求的数据,一个是没有。有符合要求的数据,那么==一定是重复的==,可以跳过!没有,那也就不用往结果列表里面追加,所以可以跳过。
所以,nums[i] == nums[i - 1]的条件下,就是可以直接跳过,不用费时间。

至于第二处的代码,也是同理。只能说巧妙。。。知识upup

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值