大裁员消息来了,希望至少挺过这个夏天,因为这个夏天很热,空调实在太贵了
刚刷到这个,真有点笑不出来。
大厂裁员风声一来,按理说大家该聊赔偿、找下家、简历怎么改,结果那个程序员憋出来一句:别的先不想,就想撑过这个夏天。
为啥?热啊。外面三十多度,屋里不开空调根本待不住,可电费又贵得肉疼。你说一个大厂程序员,平时在外人眼里好像挺光鲜,真到这种时候,脑子里想的不是股票期权,不是什么职业规划,是“这个月空调费咋办”。
这就挺职场的。
公司一句优化,下面的人开始算房租、算电费、算下个月还能不能喘口气。夏天本来就热,再碰上裁员,简直是物理意义和精神意义一起上火。
打工人最怕的不是苦,是突然没收入。热还能忍一忍,账单不会等人啊。
数组里一出现重复数,三数之和就开始恶心人了。
不是找不到答案,是答案会重复。[-1, 0, 1] 算一次,换个位置又来一次。测试用例一多,结果里堆一堆一样的三元组,看着就烦。
题目要求很简单:给一个整数数组,找出所有不重复的三元组,让它们加起来等于 0。
最笨的写法是三层循环:
for i in range(n):
for j in range(i + 1, n):
for k in range(j + 1, n):
...
这东西我一般不太信。 数据量稍微大一点,时间复杂度直接到 O(n^3),提交基本就是等超时。
这题我更习惯先排序。
排序之后有个好处: 固定一个数,剩下两个数用左右指针夹。
比如当前固定的是 nums[i],那目标就变成:
nums[left] + nums[right] == -nums[i]
小了,左指针右移。 大了,右指针左移。 刚好等于 0,收结果,然后继续跳过重复值。
代码我一般这么写:
defthree_sum(nums):
nums.sort()
ans = []
size = len(nums)
for first in range(size - 2):
# 第一个数重复,后面算出来肯定也是旧结果
if first > 0and nums[first] == nums[first - 1]:
continue
# 排序后,第一个数都大于 0 了,后面不可能凑出 0
if nums[first] > 0:
break
left = first + 1
right = size - 1
while left < right:
total = nums[first] + nums[left] + nums[right]
if total == 0:
ans.append([nums[first], nums[left], nums[right]])
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 total < 0:
left += 1
else:
right -= 1
return ans
拿个例子跑一下:
nums = [-1, 0, 1, 2, -1, -4]
print(three_sum(nums))
输出大概是:
[[-1, -1, 2], [-1, 0, 1]]
这里有两个地方容易写错。
第一个是固定位置的去重:
if first > 0and nums[first] == nums[first - 1]:
continue
这句不能省。省了以后,两个 -1 会各自跑一遍,结果自然重复。
第二个是找到答案以后,左右指针不能只移动一步。 比如数组里有一堆 0,只移动一步,后面还会撞到同样的组合。所以我这里先把当前左右值记下来,再一路跳过去。
这题的关键不是“双指针”四个字,关键是排序之后的判断顺序。
固定一个数,把三数问题拆成两数问题。 再用排序保证指针移动有方向。 最后用去重把结果清干净。
时间复杂度是 O(n^2),排序那点 O(n log n) 在这里不是大头。空间上除了结果数组,基本没额外开销。
三数之和就别硬套三层循环了,能过小样例,不代表能过测试集。