某大厂员工吐槽:直接用git pull拉代码,被diss!
刚刷到这个吐槽,给我看乐了,但又有点心酸。
一个大厂员工干活干得好好的,突然群里被点名,说他直接用git pull拉代码姿势不对。意思是你这么一拉,分支一合,提交历史就容易拧成麻花,后面真出问题,想顺着记录往回扒,能把人看麻。
这事儿其实挺真实的。很多团队嘴上说规范,文档没有,培训没有,全靠某天你踩坑了,才在大群里公开教学。
你说他菜吧,好像也不是。你说团队规范清楚吧,那更不像。
程序员最怕的不是不会,是一直以为自己会,直到某天被全群围观。
面试题:和为 K 的子数组
数组扫到第 5 个数,明明窗口里已经超过 K 了,你把左边一缩,答案反而丢了。
这题最容易写错的地方,不是代码长,而是第一眼把它当成滑动窗口。 如果数组全是正数,窗口确实能玩。可题目没说没有负数。
比如:
nums = [3, -1, 2, 1]
k = 3
你看见和大了就缩窗口,遇到 -1 这种数,前面所有判断都不稳了。负数会把窗口和拉回来,窗口的单调性没了。
这种题我一般直接换个角度:别盯着子数组本身,盯前缀和。
假设扫到当前位置的前缀和是 sum,前面某个位置的前缀和是 old。 中间这一段子数组的和就是:
sum - old
现在要它等于 k,那就是:
old = sum - k
所以问题就变成了: 每次扫到一个新位置,看看之前出现过多少次 sum - k。
这个判断比移动窗口老实多了,不赌数组有没有负数。
代码我一般这么写:
import java.util.HashMap;
import java.util.Map;
publicclassSubArrayKCounter{
publicintcount(int[] nums, int k){
Map<Integer, Integer> prefixTimes = new HashMap<>();
// 前缀和为 0 先出现过一次。
// 这行不能省,不然后面从下标 0 开始的子数组会漏掉。
prefixTimes.put(0, 1);
int sum = 0;
int hit = 0;
for (int num : nums) {
sum += num;
int need = sum - k;
Integer oldTimes = prefixTimes.get(need);
if (oldTimes != null) {
hit += oldTimes;
}
prefixTimes.put(sum, prefixTimes.getOrDefault(sum, 0) + 1);
}
return hit;
}
}
这里有个小细节,prefixTimes.put(0, 1) 很多人第一次写会漏。
举个简单的:
nums = [1, 2]
k = 3
扫到 2 的时候,sum = 3,要找的就是 sum - k = 0。 如果前缀和 0 没提前放进去,这个从下标 0 开始的 [1, 2] 就统计不到。
再看一组带负数的:
nums = [1, -1, 1, 1]
k = 1
扫的过程大概是这样:
num=1 sum=1 need=0 命中 1 次
num=-1 sum=0 need=-1 没命中
num=1 sum=1 need=0 命中 2 次
num=1 sum=2 need=1 命中 2 次
最后答案是 5。
注意这里不是找到一次就完事,因为前缀和可能重复出现。 比如前面出现过两次 sum - k,就说明以当前位置结尾的合法子数组有两个。 这也是为什么 Map 里存的不是下标,而是次数。
这题的时间复杂度是 O(n),数组只扫一遍。 空间复杂度也是 O(n),最坏情况下每个前缀和都不一样。
我见过不少人这题挂在两个地方: 一个是用滑动窗口硬写,碰到负数直接翻车;另一个是 Map 里只存一个位置,结果重复前缀和被覆盖,答案少算。
所以这题别想复杂。 前缀和负责把“连续子数组”拆成两个数相减,HashMap 负责记之前出现过几次。 剩下就是一边扫,一边补账。