39岁被大厂裁员,居然直接躺平不上班了!每天在家接送孩子、给老婆做饭,靠老婆6000元工资过日子,他却说过得比以前舒服百倍。
39岁,大厂一裁,月薪两万多的程序员直接回家当“家庭煮夫”了,这事听着离谱,细想又太真实。
很多人第一反应是,你一个大男人,靠老婆6000块工资过日子,脸上挂得住?可他说现在每天接送孩子、做饭、收拾家里,人反而松快了,睡得着,胃也不疼了,比上班那会儿舒服百倍。
你别说,这话我信。大厂那点钱,看着体面,实际上很多人是拿命换的。白天开会,晚上改bug,群消息一响心里就抽一下,工资像精神损失费。
成年人的体面,有时候真没那么重要。能把家里过顺,把自己活明白,比天天在工位上演“高薪精英”强多了。HR看了可能摇头,打工人看了只会默默羡慕。
算法题:寻找重复数
数组一跑,长度是 n + 1,数字范围却只有 1 ~ n,这种题我第一眼就不去想“怎么找”,先想一件事:它为什么一定有解。
原因很朴素,坑位只有 n 个,来了 n + 1 个数,总有一个得挤进去两次。题目叫“寻找重复数”,但真正难的地方不在“找”,在限制条件:通常不让改原数组,还希望额外空间尽量小。这时候用 set 当然能做,但味道不对,面试官多半不是想看这个。
先看最顺手的写法,能过,逻辑也直。
deffind_duplicate(nums):
seen = set()
for x in nums:
if x in seen:
return x
seen.add(x)
这段代码没毛病,时间复杂度 O(n),空间复杂度 O(n)。平时业务代码里我其实挺愿意这么写,尤其是数据量不大时,清楚、省心、不容易写错。
但这题真正该看的,是怎么把数组当成“链表”来做。
为什么能这么想?因为数组里的值都在 1 ~ n 之间,而下标是可以一路跳的。比如你现在在位置 i,下一步就跳到 nums[i]。这样每个位置都会指向下一个位置,重复数意味着某个位置被多次指向,最后一定会形成环。
一旦想到“环”,基本就是快慢指针了。
deffind_duplicate(nums):
slow = nums[0]
fast = nums[0]
whileTrue:
slow = nums[slow]
fast = nums[nums[fast]]
if slow == fast:
break
p1 = nums[0]
p2 = slow
while p1 != p2:
p1 = nums[p1]
p2 = nums[p2]
return p1
这段代码第一次看着有点绕,但抓住两步就行。
第一步,快指针一次走两步,慢指针一次走一步,只要有环,它们一定会在环里碰上。
第二步,一个指针回到起点,一个留在相遇点,然后都一步一步走,再次相遇的位置,就是重复数。
拿 nums = [1,3,4,2,2] 过一遍:
# 下标: 0 1 2 3 4
# 数组: 1 3 4 2 2
# 走法: 0 -> 1 -> 3 -> 2 -> 4 -> 2 ...
看到这条链就明白了,2 -> 4 -> 2 这里已经成环,入口就是重复数 2。
这题有意思的地方就在这儿:表面是数组,实际考的是映射关系;表面是找重复,实际是找环入口。
很多人做这题会卡在“为什么返回的是环入口”。这个地方别硬背结论,画一下路径,或者自己拿一组数据手推两遍,马上顺。算法题里不少题都这样,代码不长,真正值钱的是你能不能把模型换过来。
所以这题我一般会记两种解法: 一种 set,写得快,适合先把结果做出来。 一种快慢指针,空间 O(1),这是这题更像样的解法。