力扣 15 三数之和笔记:重复出在两个地方

举报
yd_274940399 发表于 2026/11/07 17:53:01 2026/11/07
【摘要】 题目是给一个整数数组,找出全部和为 0 的三元组,要求下标互不相同,答案里不能有重复的三元组。我先写的是三层循环的笨办法,写完才动手优化。这篇主要记后面去重那一段:双指针本身不难,我卡的是重复结果从哪冒出来的。 三层循环任意取三个下标,组合全枚举一遍,和为 0 的收下来。题目不让重复,我直接用集合装排序好的三元组去重:import itertoolsdef brute(nums): r...

题目是给一个整数数组,找出全部和为 0 的三元组,要求下标互不相同,答案里不能有重复的三元组。我先写的是三层循环的笨办法,写完才动手优化。这篇主要记后面去重那一段,重复结果从哪冒出来,我一开始没想明白。

三层循环

任意取三个下标,组合全枚举一遍,和为 0 的收下来。题目不让重复,我直接用集合装排序好的三元组去重:

import itertools

def brute(nums):
    res = set()
    for i, j, k in itertools.combinations(range(len(nums)), 3):
        if nums[i] + nums[j] + nums[k] == 0:
            res.add(tuple(sorted((nums[i], nums[j], nums[k]))))
    return res

官方例子能过:[-1, 0, 1, 2, -1, -4] 得到 [-1, -1, 2] 和 [-1, 0, 1],[0, 1, 1] 是空,[0, 0, 0] 得到一组 [0, 0, 0]。

n 个数就要跑 C(n, 3) 次循环体。n 取 60 时是 34,220 次,n 到题目上限 3000 时这个数是 4.5×10⁹ 量级,没法交。

固定第一个数

三数之和固定住第一个数,剩下就是在后面的数里找两个,和等于第一个数的相反数。两数之和如果每次还用两层循环,总的复杂度降不下来,所以先把数组排序:排完序,剩下的两数用一左一右两个指针夹。和小于目标,左指针右移把和抬大;和大于目标,右指针左移把和压小。方向能这么定,是因为数组有序。

排序还顺带解决了一半去重问题:相同的值排完会挨在一起,要跳过重复,只需要和相邻位置比。

按这个思路写,结构就是枚举 i,在 i 的右边跑双指针。下面这版是故意没写任何跳过重复的,我拿它测重复到底从哪来。

重复的两个来源

输入 [-2, 0, 0, 2, 2],没去重的版本输出了两份 [-2, 0, 2]。过程是这样的:第一个数取 -2,左指针停在第一个 0,右指针停在最后一个 2,命中,记下;两个指针各走一步,左指针到了第二个 0,右指针到了前一个 2,还是同样的三个数,又命中一次。

换一组更挤的:[-1, -1, -1, 2, 2] 输出了三份 [-1, -1, 2]。多出来的第三份来源不同:第一个数取到第二个 -1 时,它右边能凑出的组合和取第一个 -1 时完全一样,整个搜索等于重做了一遍。

两处来源,要堵两个地方:

  • 第一个数和前一个相同,跳过,不再枚举。
  • 命中一组之后,左右指针先把两边相同的数值都跳完,再继续走。

两个地方都补上之后,上面两组输入都只剩一组结果。[0, 0, 0, 0] 也测了,输出一组 [0, 0, 0]。

完整的代码

class Solution:
    def threeSum(self, nums):
        nums.sort()
        res = []
        n = len(nums)
        for i in range(n - 2):
            if nums[i] > 0:
                break
            if i > 0 and nums[i] == nums[i - 1]:
                continue
            l, r = i + 1, n - 1
            while l < r:
                s = nums[i] + nums[l] + nums[r]
                if s < 0:
                    l += 1
                elif s > 0:
                    r -= 1
                else:
                    res.append([nums[i], nums[l], nums[r]])
                    l += 1
                    r -= 1
                    while l < r and nums[l] == nums[l - 1]:
                        l += 1
                    while l < r and nums[r] == nums[r + 1]:
                        r -= 1
        return res

里面还有一个提前结束:排完序后第一个数大于 0,它后面的数只会更大,三个正数凑不出 0,枚举可以直接停。

验证

  • 官方三例重测,结果和上面写的一致,另外补了 [0, 0, 0, 0],只输出一组。
  • 随机对拍:数组长度 0 到 9,元素只从 -3 到 3 里取。值域故意压窄,是想让重复元素扎堆,去重逻辑有问题更容易当场暴露。3000 组和三层循环加集合去重的笨办法对照,零失配。
  • 计数对比:n = 60 的随机数组,三层循环要跑 34,220 次循环体;双指针这版我数了内层步数,421 步。

复杂度:外层枚举第一个数是 O(n),每个数配一遍双指针 O(n),总时间 O(n²),排序的 O(n log n) 被盖在下面。多用的空间主要是排序本身。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。