某大厂程序员:大裁员消息来了,希望至少挺过这个夏天,因为这个夏天很热,空调实在太贵了
某大厂要裁员,办公室里大家表面上还在敲代码、开会、改需求,其实心里都在算日子。结果有个程序员来了一句:别的先不说,能不能让我熬过这个夏天。
因为天太热了,家里开空调电费顶不住。
以前说大厂人焦虑,大家还觉得是不是年包太高了凡尔赛。现在你看,焦虑已经不是买不买房、跳不跳槽了,是能不能多蹭几个月公司的冷气。
打工人有时候真不是多贪,就想这个月工资正常发,下个月社保别断,夏天有个凉快地方坐着干活。
听起来很没出息,但这就是很多人的现实。挺心酸的。
数组里一堆数,要求找出所有 a + b + c = 0 的三元组。
这题最烦的地方不是三数相加,而是去重。 我看很多人第一次写,会直接三层循环怼上去:
for i
for j
for k
能不能跑?小数据当然能跑。 但数据一大,马上就开始磨机器,时间复杂度 O(n^3),这种代码我一般不会往下看,面试里也基本没救。
这题真正要盯住两个点:
一个是数组先排序。 另一个是固定一个数,剩下两个数用双指针往中间夹。
排序以后,数组从小到大排好,比如:
[-4, -1, -1, 0, 1, 2]
固定第一个数 nums[i]。
然后左指针放在 i + 1,右指针放在数组最后。
如果三个数加起来小于 0,说明左边这个数太小了,左指针往右挪。
如果三个数加起来大于 0,说明右边这个数太大了,右指针往左挪。
如果刚好等于 0,记录答案,然后两边都要继续挪,并且跳过重复值。
代码我会这么写:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
publicclassThreeSumChecker{
public List<List<Integer>> collectZeroTriples(int[] nums) {
List<List<Integer>> triples = new ArrayList<>();
if (nums == null || nums.length < 3) {
return triples;
}
Arrays.sort(nums);
for (int fixed = 0; fixed < nums.length - 2; fixed++) {
if (nums[fixed] > 0) {
break;
}
if (fixed > 0 && nums[fixed] == nums[fixed - 1]) {
continue;
}
int left = fixed + 1;
int right = nums.length - 1;
while (left < right) {
int sum = nums[fixed] + nums[left] + nums[right];
if (sum == 0) {
triples.add(Arrays.asList(nums[fixed], nums[left], nums[right]));
int leftValue = nums[left];
int rightValue = nums[right];
while (left < right && nums[left] == leftValue) {
left++;
}
while (left < right && nums[right] == rightValue) {
right--;
}
continue;
}
if (sum < 0) {
left++;
} else {
right--;
}
}
}
return triples;
}
}
这里有几个地方不能随手改。
第一,fixed > 0 && nums[fixed] == nums[fixed - 1] 这个判断是给第一个数去重的。
比如数组里有两个 -1,如果不跳过第二个 -1,同样的三元组会被算两次。
第二,找到一组答案以后,不能只写:
left++;
right--;
这地方我以前见过不少 bug。
比如:
[-2, 0, 0, 0, 2, 2]
固定 -2,左边多个 0,右边多个 2。 如果不跳过重复值,结果里会塞进重复的 [-2, 0, 2]。
所以代码里我先把当前的 leftValue 和 rightValue 存下来,再用 while 一口气跳过去。这个写法不花哨,但不容易出脏数据。
第三,nums[fixed] > 0 可以直接 break。
因为数组已经排好序了。当前固定值都大于 0,后面的数只会更大,三个正数加起来不可能等于 0。继续扫就是浪费。
这题的时间复杂度是 O(n^2)。 排序是 O(n log n),外层一层循环,里面左右指针最多扫一遍,所以主要耗时还是 O(n^2)。
空间上不算返回结果,基本就是 O(1)。当然 Java 的排序内部实现会有一些额外开销,这个没必要在业务代码里硬抠。
三数之和看起来是数组题,其实考的是排序以后怎么减少无效枚举。 别一上来就三层循环,也别只顾着凑出答案,去重没处理干净,这题就等于没写完。