线上出了 Bug,全组都在找原因。最后发现是因为我改动了一个公共类导致的。复盘会上,我正准备背锅,隔壁老兵抢先开口了
线上一出 Bug,会议室空气都不对了。大家查日志、翻提交、对接口,忙得跟捞针一样,最后一锤子敲到我头上:公共类是我改的。
我那会儿都准备把锅端稳了,结果隔壁那个老兵先开口,味儿一下就不一样了。他没玩那套“谁改谁背”,直接点破:问题不是某个人手抖,是这条冷门链路压根没在压测环境里走过,公共类改完也没自动回归兜底,光靠开发自己测,基本等于拿肉眼防地雷。
底下有人听完直点头,说白了,这种锅本来就不该让一个人硬吃。也有人吐槽,很多团队平时喊流程喊得震天响,真到公共模块变更,防线薄得像纸。
职场干久了就知道,真靠谱的同事不是会甩锅的,是关键时候能把锅拆开看的人。不然复盘一圈,问题没补上,下次还得炸。
面试题:摘樱桃
这题一眼看过去,很多人会走偏。
看到“去一趟,再回来一趟”,就真按两次 DFS 写。第一趟贪心摘多一点,第二趟再补。结果很快就发现不对:前面走法一变,后面整张图就跟着变,樱桃被摘掉之后,回程路径的最优解根本不是独立问题。
这种题我一般不信“来回各算一次”。它更像是两个人同时从 (0,0) 出发,一起走到 (n-1,n-1)。为什么能这么转?因为一个人去、一个人回,等价成两个人同步往前走。两个人每走一步,总步数都是一样的,假设现在都走了 k 步,那只要知道一个人在 (x1, y1),另一个人在 (x2, y2),其中 y1 = k - x1,y2 = k - x2,状态就定下来了。
这时候 DP 味道就出来了。
定义 dp[k][x1][x2]:两个人同时走了 k 步,第一个人在 x1,第二个人在 x2 时,最多能摘多少樱桃。至于列坐标,不用单独存,直接算:y1 = k - x1y2 = k - x2
有几个地方很容易写崩:
第一,格子越界直接跳过。 第二,碰到 -1 的刺也直接跳过。 第三,两个人如果落在同一个格子,这颗樱桃只能算一次,别手一抖加了两遍。这个地方在线上写业务代码时也挺像,幂等没做好,账就重复记了。
转移也不复杂。因为每个人每次只能“向右”或者“向下”,所以上一步一共 4 种来源:
// (x1-1, x2-1) 两人都从上面来
// (x1-1, x2) 1号从上面来,2号从左边来
// (x1, x2-1) 1号从左边来,2号从上面来
// (x1, x2) 两人都从左边来
核心代码我一般会写成这样,够用了:
publicintcherryPickup(int[][] grid){
int n = grid.length;
int maxStep = 2 * n - 2;
int[][][] dp = newint[maxStep + 1][n][n];
for (int k = 0; k <= maxStep; k++) {
for (int i = 0; i < n; i++) {
Arrays.fill(dp[k][i], Integer.MIN_VALUE);
}
}
dp[0][0][0] = grid[0][0];
for (int k = 1; k <= maxStep; k++) {
for (int x1 = Math.max(0, k - (n - 1)); x1 <= Math.min(n - 1, k); x1++) {
for (int x2 = Math.max(0, k - (n - 1)); x2 <= Math.min(n - 1, k); x2++) {
int y1 = k - x1, y2 = k - x2;
if (grid[x1][y1] == -1 || grid[x2][y2] == -1) {
continue;
}
int best = Integer.MIN_VALUE;
for (int p1 = x1 - 1; p1 <= x1; p1++) {
for (int p2 = x2 - 1; p2 <= x2; p2++) {
if (p1 >= 0 && p2 >= 0) {
best = Math.max(best, dp[k - 1][p1][p2]);
}
}
}
if (best == Integer.MIN_VALUE) {
continue;
}
int gain = grid[x1][y1];
if (x1 != x2 || y1 != y2) {
gain += grid[x2][y2];
}
dp[k][x1][x2] = best + gain;
}
}
}
return Math.max(0, dp[maxStep][n - 1][n - 1]);
}
时间复杂度是 O(n^3),别被三维数组吓到,这题就该这么解。真正难的不是代码量,是能不能把“来回一趟”想通成“两个人同步向前”。这一步一旦拧过来,后面基本就是体力活。
这题我觉得挺典型:表面是路径搜索,实际考的是状态设计。状态一旦设计别扭,后面不是超时,就是重复计数,再不然就是被 -1 卡死。算法题很多时候就这样,不是你不会写,是你第一眼信错了方向。