找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

826

积分

0

好友

102

主题
发表于 昨天 18:35 | 查看: 1| 回复: 0

刚在网上看到一个帖子,说的是老公失业在家,动不动就躲着不吭声。老婆这边呢,一边觉得心烦,一边又感到无奈,觉得人到中年了,怎么一点处理问题的能力都没有。

社交媒体关于中年失业与家庭沟通的讨论截图

要我说,这事儿的关键可能不只是“失业”本身,而是两个人都陷进了情绪里。他怕丢脸,就选择当“缩头乌龟”;她既要家里的收入稳定,又希望对方能主动处理好情绪——这多少有点“既要又要还要”的味道了。

其实,失业说白了是家庭需要共同面对的风险,不是某个人的“耻辱”。作为当事人,最起码得做到把情况摊开说清楚,列个计划。哪怕是先去送送外卖、干点临时工,也是在传递“我在行动”的信号,总比闷头打游戏干等着强。另一方呢,也别把“烦死了”总挂在嘴边,试着问一句“咱们接下来该怎么办”,可能比单纯抱怨更有用。

算法题:三数之和

昨天晚上十一点多,我在公司楼下拿着奶茶吹风,本来想摸鱼刷会儿视频,结果手一滑点开了力扣,一道老熟人题目蹦了出来:「三数之和」。那一瞬间整个人都清醒了,不知道你们有没有过这种体验——看到熟悉的题目,脑子比灌了咖啡还提神。

先说说这题要干嘛。简单讲,就是给你一个整数数组 nums,要你找出里面所有 不重复 的三元组 (a, b, c),并且满足 a + b + c = 0。这里有两个关键点:一是情况不能漏,二是结果不能重复。比如 [-1, -1, 2] 这个组合只能算一次,顺序调换不算新组合。

很多人的第一反应(包括当年的我)就是:三层 for 循环暴力破解,反正能写出来就行:

def three_sum_bruteforce(nums):
    n = len(nums)
    res = []
    for i in range(n):
        for j in range(i + 1, n):
            for k in range(j + 1, n):
                if nums[i] + nums[j] + nums[k] == 0:
                    triplet = sorted([nums[i], nums[j], nums[k]])
                    if triplet not in res:   # 去重,超级慢
                        res.append(triplet)
    return res

这个写法问题很明显:首先,时间复杂度是 O(n³),数据量稍大肯定超时;其次,你看那个 triplet not in res,在列表里查找又是一层循环,简直是雪上加霜。总结就是:能跑,但既慢又丑。

我在楼下抽了根烟想了想,这题其实有一个非常经典的套路:排序 + 双指针。很多数组相关的题目都吃这一套,属于学会了能受益很久的那种。

让我用大白话把思路捋一遍:

  • 先排序:例如数组 [-1, 0, 1, 2, -1, -4],排完序变成 [-4, -1, -1, 0, 1, 2]
  • 固定第一个数:从左到右,依次把 nums[i] 当作三元组里的第一个数固定下来。
  • 转化问题:那么剩下的任务就变成了——在 i 右边的子数组里,找到两个数,让它们的和等于 -nums[i]
  • 双指针解决:在一个有序区间里找“两数之和”,最顺手的就是双指针法。左指针指向剩余区间最左端,右指针指向最右端。如果三数之和大于0,说明太大了,右指针左移;如果小于0,说明太小了,左指针右移。

光说可能有点绕,直接看代码就一目了然了。下面是一份比较规整、可以直接提交的Python实现:

from typing import List

def three_sum(nums: List[int]) -> List[List[int]]:
    nums.sort()              # 1. 先排序
    n = len(nums)
    res = []

    for i in range(n):
        # 小优化:第一个数已经大于 0,那后面全是非负,肯定不可能凑出 0 了
        if nums[i] > 0:
            break

        # 跳过相同的第一个数,避免结果重复
        if i > 0 and nums[i] == nums[i - 1]:
            continue

        left = i + 1
        right = n - 1

        while left < right:
            s = nums[i] + nums[left] + nums[right]
            if s == 0:
                res.append([nums[i], nums[left], nums[right]])

                # 这两个 while 是关键:跳过相同的第二、第三个数
                left_val = nums[left]
                right_val = nums[right]
                while left < right and nums[left] == left_val:
                    left += 1
                while left < right and nums[right] == right_val:
                    right -= 1

            elif s < 0:
                # 和太小了,说明需要更大一点的数,左指针右移
                left += 1
            else:
                # 和太大了,需要更小一点的数,右指针左移
                right -= 1

    return res

这个版本就优雅多了,整体时间复杂度是 O(n²),属于面试官看了会点头的那种。有几个细节值得特别唠叨一下:

第一,为什么可以 if nums[i] > 0: break
因为数组已经排好序了,nums[i] 是当前能选到的最小的数。如果它都已经大于0,那它后面的所有数也都 ≥ 它,三个正数相加怎么可能等于0呢?直接结束循环,能省下很多无用的计算。

第二,“去重”到底在去什么?
这题最烦人的地方其实不是算法思路,而是各种重复。重复可能发生在三个地方:

  • 第一个数重复if i > 0 and nums[i] == nums[i - 1]: continue
  • 第二个数重复:找到一个有效解后,while left < right and nums[left] == left_val: left += 1
  • 第三个数重复:同理,对 right 指针也进行相同的跳过操作

如果不这么处理,想象一下数组里有多个 -1,那么 i 相同、left 相同、right 相同的组合会被多次加入结果列表,输出一堆重复的三元组,这在面试中可是大忌。

第三,别忘了边界情况。
我昨晚在楼下想的时候,风一吹差点把这茬忘了:

  • 数组长度小于3,直接返回空列表。
  • 数组元素全为正数或全为负数(如 [1, 2, 3, 4]),排序后第一个数就大于0,前面的优化会直接 break,返回正确结果 []
  • [0, 0, 0, 0] 这种情况,按我们的写法也只会留下一个 [0, 0, 0],不会重复也不会遗漏。

可以写个简单的测试用例跑一下,心里更踏实:

if __name__ == "__main__":
    nums = [-1, 0, 1, 2, -1, -4]
    print(three_sum(nums))
    # 可能的输出(顺序不重要):
    # [[-1, -1, 2], [-1, 0, 1]]

看一下就知道逻辑是对的:排序后是 [-4, -1, -1, 0, 1, 2],能凑成0的就是那两组。

最后一个小提示:
如果你在刷题时发现,两数之和、三数之和、四数之和这一系列题目,其实都是同一个模子刻出来的,只是维度不同。两数之和可以用哈希表;三数之和就变成了“固定一个数 + 两数之和(双指针版)”;四数之和则是“固定两个数 + 两数之和”。套路打通之后,后面这些题基本都能迎刃而解。

好了,奶茶喝完了,算法也聊完了。我得把这思考过程记下来,分享到开发者广场和大家交流。如果你正好也在刷题,建议把上面的代码亲手敲一遍,别直接复制,自己敲一遍记忆更深刻。




上一篇:Flink CDC 面试三大核心问题解析:无锁读取、Exactly-Once 与增量快照
下一篇:金融男用AI开发反人性App:祷告解锁手机,TikTok广告半年赚14万+
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-31 03:25 , Processed in 0.335363 second(s), 38 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表