38岁的总监,某大厂P9,被裁了。现在在卖保险!
我在网上看到一个帖子,味儿一下就上来了:38岁的总监,某大厂P9,去年被裁,转头去卖保险了,而且第一个月就冲进了MDRT。。
网友们看完也挺热闹。有人说,大厂P9失业后去卖保险,这哪是转行,分明是把“向上管理”和“资源整合”切到新地图了。还有人调侃,普通人被裁了先怀疑人生,P9被裁了先怀疑哪家客户还没薅到。话糙理不糙,职位做到这个级别,最值钱的还真不一定是那张工牌,而是脑子、圈子和那套见人说人话见项目讲人话的本事。
大厂身份会过期,能搞定人的本事,反倒更像硬通货。
面试题:轰炸敌人
这道“轰炸敌人”我第一次看到时,直觉是暴力枚举:把每个空位 0 都试一遍,向上、下、左、右扫过去,遇到墙 W 停,统计能炸到多少个敌人 E。写出来不难,但一旦网格大一点,性能就不太好,空位一多,很多行列会被重复扫描。
这个题真正该盯住的地方,不是“怎么炸”,而是“同一段没有墙隔开的行和列,其实可以复用统计结果”。
比如这一行:
0 E 0 0 W E E 0
在 W 左边这一段里,不管你站在哪个 0 上,横向能炸到的敌人数其实是一样的。列也是同理。那就没必要每个空位都重新扫四个方向了。
我一般会这么写: 遍历网格时,维护两个量:
rowHits:当前格子所在这一段横向能打到多少敌人colHits[j]:第j列当前这一段纵向能打到多少敌人
只有在“这一段刚开始”的时候才重新统计。也就是:
当前列是 0,或者左边是墙,重算 rowHits当前行是 0,或者上边是墙,重算 colHits[j]
核心代码我给你贴完整一点,Java 写法如下:
classSolution{
publicintmaxKilledEnemies(char[][] grid){
if (grid == null || grid.length == 0 || grid[0].length == 0) {
return0;
}
int m = grid.length;
int n = grid[0].length;
int[] colHits = newint[n];
int ans = 0;
for (int i = 0; i < m; i++) {
int rowHits = 0;
for (int j = 0; j < n; j++) {
if (j == 0 || grid[i][j - 1] == 'W') {
rowHits = 0;
for (int k = j; k < n && grid[i][k] != 'W'; k++) {
if (grid[i][k] == 'E') {
rowHits++;
}
}
}
if (i == 0 || grid[i - 1][j] == 'W') {
colHits[j] = 0;
for (int k = i; k < m && grid[k][j] != 'W'; k++) {
if (grid[k][j] == 'E') {
colHits[j]++;
}
}
}
if (grid[i][j] == '0') {
ans = Math.max(ans, rowHits + colHits[j]);
}
}
}
return ans;
}
}
这个写法有个挺实在的点:虽然看起来里面还有两层 for,但不是每个位置都把整行整列扫一遍。每一段连续区域只会被统计一次,所以整体复杂度能压到 O(m * n),空间复杂度是 O(n)。
这题还有个容易写错的地方:只能把炸弹放在 0 上,不能放在敌人 E 上;另外,炸弹传播时遇到 W 就必须停,墙后面的敌人不算。这两个条件如果漏一个,结果基本就偏了。
拿样例看一下:
char[][] grid = {
{'0','E','0','0'},
{'E','0','W','E'},
{'0','E','0','0'}
};
中间那个 grid[1][1] 就是最优点,横着能炸左边一个,竖着能炸上边和下边各一个,一共 3 个。
这种题不算特别难,但挺适合练“重复计算怎么消掉”。面试里如果你一上来就是四方向暴力,其实也还能聊;但再往下走一步,把“按墙分段复用统计值”说出来,代码味道就不一样了。