上周面试一个技术岗,前30分钟聊得不错。问到离职原因,他顿了顿 “跟部门一个同事闹了矛盾,领导偏私,搞得每天上班很压抑,索性走了。
我在网上看到个帖子,HR吐槽得挺真实:上周面了个技术岗,前面聊得都挺顺,项目、技术、思路都在线,眼看着像是能进下一轮了。结果一问离职原因,哥们直接来一句:跟同事闹矛盾,领导还拉偏架,天天上班压抑,干脆走人。
这话吧,听着像大实话,可放在面试桌上,味儿就变了。HR心里大概率会嘀咕:你今天能跟前同事闹翻,明天会不会跟我们团队也来一场“程序员武斗大会”?
我自己的感觉是,离职原因可以说委屈,但别说成情绪宣泄。换成“团队协作方式和我期待不太一致,想找个沟通更顺畅的环境”,意思到了,体面也还在。毕竟找工作不是吐槽大会,谁先上头,谁就容易被Pass。
面试题:水壶问题
这道“水壶问题”看着像脑筋急转弯,真写成代码时,很多人第一反应还是模拟倒水过程:装满、倒空、互相倾倒,一步一步搜。这样能做,但题目其实有个更稳的切口,不用真把水来回倒一遍。
题意一般是这样:给你两个容量分别为 x 和 y 的水壶,问能不能凑出恰好 target 升水。
我第一次看到这题时,也想过用 BFS 把所有状态跑出来,比如 (a,b) 表示两个壶当前的水量,然后枚举 6 种操作:装满 x、装满 y、倒空 x、倒空 y、x 倒给 y、y 倒给 x。这个思路没错,但实现偏重,状态一多还得去重。
这题真正好用的是数学判断。
先看两个很直观的边界:
if (target > x + y) returnfalse;
if (target == 0) returntrue;
因为两个壶加起来都不够,肯定凑不出来。 再往下看,问题其实变成:target 能不能由 x 和 y 的某种倒水操作组合出来。最后会落到一个结论上——只要 target 是 gcd(x, y) 的倍数,就有机会凑出来。
核心代码其实很短:
classSolution{
publicbooleancanMeasureWater(int x, int y, int target){
if (target == 0) returntrue;
if (x + y < target) returnfalse;
if (x == 0 || y == 0) return target == x || target == y;
return target % gcd(x, y) == 0;
}
privateintgcd(int a, int b){
while (b != 0) {
int t = a % b;
a = b;
b = t;
}
return a;
}
}
比如 x=3, y=5, target=4。gcd(3,5)=1,4 能被 1 整除,所以一定能做出来。现场过程也很经典:
// 5装满 -> 倒给3,剩2
// 3倒空 -> 2倒给3
// 5再装满 -> 倒给3,5里剩4
这题有意思的地方就在这里:代码里你并没有真的去模拟“剩 2、再倒、再装满”这些动作,但数学已经替你把可达性算完了。
当然,面试里如果面试官继续追问“能不能输出具体步骤”,那就不能只写 gcd 了,得回到搜索。可以用 BFS 存状态:
Queue<int[]> q = new LinkedList<>();
Set<Long> visited = new HashSet<>();
q.offer(newint[]{0, 0});
不过这已经是下一层问题了。 如果题目只是“能不能量出 target”,那 gcd 解法更合适,代码短,边界清楚,时间复杂度也就是求一次最大公约数。
这类题我一直觉得挺像工程里的排查:表面上看是流程问题,真往下拆,最后卡住的往往不是“怎么做”,而是“有没有必要做那么多”。水壶题也是一样,真模拟能做,但没必要。数学条件一出来,代码立刻就收住了。