程序员老鬼

上周面试一个技术岗,前30分钟聊得不错。问到离职原因,他顿了顿 “跟部门一个同事闹了矛盾,领导偏私,搞得每天上班很压抑,索性走了。

我在网上看到个帖子,HR吐槽得挺真实:上周面了个技术岗,前面聊得都挺顺,项目、技术、思路都在线,眼看着像是能进下一轮了。结果一问离职原因,哥们直接来一句:跟同事闹矛盾,领导还拉偏架,天天上班压抑,干脆走人。

Image

这话吧,听着像大实话,可放在面试桌上,味儿就变了。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 解法更合适,代码短,边界清楚,时间复杂度也就是求一次最大公约数。

这类题我一直觉得挺像工程里的排查:表面上看是流程问题,真往下拆,最后卡住的往往不是“怎么做”,而是“有没有必要做那么多”。水壶题也是一样,真模拟能做,但没必要。数学条件一出来,代码立刻就收住了。