和领导大吵一架,第二天就被裁员了,两周后拿了月薪 1.5w的 offer,没想到HR却发消息,你不用来了~
跟领导狠狠干了一架,第二天人就没了,这剧情已经够窒息了。结果两周后好不容易拿到1.5w offer,HR又甩来一句:有人把你的事说了,你别来了。
网友一个说,这不就是前司在背后使绊子
我看这种事,吵架本身都未必最致命,致命的是有些领导气量跟工位一样大,表面让你走,背地里还得补一刀。HR那句“你不用来了”也挺灵性,轻飘飘几个字,能把人血压顶上去。打工人平时真别高估“体面分手”这四个字,很多公司嘴上讲流程,手上全是江湖。
面试题:粉碎糖果
糖果一把一把往下掉,最烦的不是消不掉,是真以为“扫一遍矩阵”就完了。 这题叫“粉碎糖果”,看着像模拟,真写的时候最容易翻车的地方有两个:一是这一轮该消的必须先全部标记,不能边扫边改;二是下落不能偷懒,别想着挨个交换,直接按列压缩更稳。
先说现象。 如果你一边遍历一边把糖果置 0,后面本来该一起消掉的块,可能就漏了。尤其是横着三个、竖着三个交叉的时候,现场会很难看。这个地方我一般不信“边扫边处理”这种写法,算法题里这种顺手优化,经常就是 bug 的起点。
做法其实很朴素:
先遍历整个棋盘,把所有连续 3 个及以上的糖果标记出来; 再统一清零; 然后每一列做一次“下落”; 如果这一轮发生过消除,就继续重复,直到棋盘稳定。
这种题不是拼花活,拼的是你能不能把状态改得干净。
Java 代码我给你写个能直接过的版本,核心逻辑就这几步:
classSolution{
publicint[][] candyCrush(int[][] board) {
int m = board.length, n = board[0].length;
boolean changed = true;
while (changed) {
changed = false;
// 1. 标记横向
for (int i = 0; i < m; i++) {
for (int j = 0; j < n - 2; j++) {
int v = Math.abs(board[i][j]);
if (v != 0 && v == Math.abs(board[i][j + 1]) && v == Math.abs(board[i][j + 2])) {
board[i][j] = -v;
board[i][j + 1] = -v;
board[i][j + 2] = -v;
changed = true;
}
}
}
// 2. 标记纵向
for (int i = 0; i < m - 2; i++) {
for (int j = 0; j < n; j++) {
int v = Math.abs(board[i][j]);
if (v != 0 && v == Math.abs(board[i + 1][j]) && v == Math.abs(board[i + 2][j])) {
board[i][j] = -v;
board[i + 1][j] = -v;
board[i + 2][j] = -v;
changed = true;
}
}
}
// 3. 下落
for (int j = 0; j < n; j++) {
int write = m - 1;
for (int i = m - 1; i >= 0; i--) {
if (board[i][j] > 0) {
board[write--][j] = board[i][j];
}
}
while (write >= 0) {
board[write--][j] = 0;
}
}
}
return board;
}
}
这里有个细节要注意: 我用负数做“待消除标记”,判断的时候统一取 Math.abs()。这不是为了炫技,是因为同一轮里,一个糖果可能既属于横向三连,也属于纵向三连。你要是直接改成 0,后面的判断链就断了。
再看复杂度。单轮扫描是 O(m * n),最坏会做多轮,但这题数据规模不大,老老实实模拟就够了,没必要提前把自己绕进去。
这种题我一般就盯两件事:标记和执行要分开。下落别写成一堆 swap。
写对了不难,难的是别在这些小地方手滑。很多人不是不会做,是代码一写长,状态就乱了。这个题就很典型。参考的整体技术写作气质我已经按更偏现场感的方式收过了,没有照搬原文结构。