程序员老鬼

面试官:线上突然大量报错,你先查什么? 我:先查今天谁发了版

线上突然一片报错,我第一反应通常不是冲进代码里找 if else。

先看今天谁发了版。

Image

这话听着像段子,其实一点不玄学。线上大量报错这种事,最怕一上来埋头翻业务代码,翻半小时,最后发现只是 10 分钟前刚发过版,某个配置没带上,某个开关值写反了,或者一个本地没问题、线上才炸的默认参数被顺手改了。

排查顺序我一般很土:先看发布记录,再看监控告警,再捞错误日志和 trace。 发布时间和报错时间能不能对上,这是第一刀。能对上,先怀疑变更;对不上,再去看流量、依赖服务、数据库、缓存、MQ 这些基础设施。

很多人觉得“先查谁发版”是在甩锅。我倒不这么看。线上排障不是写复盘报告,先缩小范围最重要。一次大面积报错,八成不是代码凭空抽风,要么变更,要么依赖抖了,要么配置动了

算法题:连续空余座位

凌晨两点,刷题群里丢了个题目:连续空余座位。看着像模拟,其实坑点全在“连续”两个字上。要的不是空座位个数,是能不能找到一段连续的、长度满足条件的空位区间。

题目抽象一下:给一个数组,1 表示已占用,0 表示空位。问是否存在长度为 k 的连续 0。

我第一眼就不太信那种“遍历一遍统计总数”的写法。连续问题,最忌讳的就是把它写成计数问题。只要中间断一次,前面的就作废。

直接上代码,核心逻辑就几行:

publicclassSeatFinder{

publicbooleanhasContinuousSeats(int[] seats, int k){
int count = 0;

for (int i = 0; i < seats.length; i++) {
if (seats[i] == 0) {
                count++;
if (count >= k) {
returntrue;
                }
            } else {
                count = 0; // 一旦被打断,重新开始
            }
        }

returnfalse;
    }
}

就这么简单?对,连续问题,往往就是这种“滚动计数 + 断点清零”。

但实际写业务的时候,数组往往不是给你现成的 0/1,而是一堆座位对象,比如:

classSeat{
privateint row;
privateint col;
privateboolean occupied;

publicbooleanisOccupied(){
return occupied;
    }
}

那你就别再额外构造一个 int[] 了,直接按顺序扫。

publicbooleanhasContinuousSeats(List<Seat> seatList, int k){
int count = 0;

for (Seat seat : seatList) {
if (!seat.isOccupied()) {
            count++;
if (count == k) {
returntrue;
            }
        } else {
            count = 0;
        }
    }

returnfalse;
}

注意一个细节:这里用 == k 也行,用 >= k 也行。但如果题目是“刚好 k 个”,那判断条件就得改。很多人写题不仔细,这里容易被反杀。

再往下想一步,如果题目升级——不只是判断有没有,而是要返回那段连续区间的起始下标呢?

这时候就不能只记 count 了,还得记起点。

publicintfindStartIndex(int[] seats, int k){
int count = 0;
int start = -1;

for (int i = 0; i < seats.length; i++) {
if (seats[i] == 0) {
if (count == 0) {
                start = i; // 记录连续段起点
            }
            count++;

if (count == k) {
return start;
            }
        } else {
            count = 0;
            start = -1;
        }
    }

return -1;
}

这里我习惯在 count == 0 的时候记录 start,而不是等到满足 k 再去倒推 i - k + 1。两种都行,但前者在调试时更直观,尤其是你想打日志看连续区间的时候。

再说个容易被忽略的点:如果座位是按“排”来给的,比如二维数组,每一排单独算连续,中间不能跨排。

那就别偷懒 flatten 成一维。直接按行处理:

publicbooleanhasContinuousSeats2D(int[][] seats, int k){
for (int row = 0; row < seats.length; row++) {
int count = 0;
for (int col = 0; col < seats[row].length; col++) {
if (seats[row][col] == 0) {
                count++;
if (count >= k) {
returntrue;
                }
            } else {
                count = 0;
            }
        }
    }
returnfalse;
}

这种题目,说白了就是状态机。只有两个状态:当前连续长度是多少,被没被打断。

别一上来就想着什么滑动窗口模板。滑动窗口适合“固定长度区间求和”“最长子串”那类问题。这个题不需要左右指针来回动,一个指针够了。

我见过有人写成两层循环,外层枚举起点,内层检查后面 k 个是不是 0。时间复杂度直接从 O(n) 退化成 O(nk)。数据量一大,直接凉。

连续问题的套路就一句话:顺序扫描,遇到断点立即清零。别留恋。

很多算法题,其实不是难在思路,而是难在你会不会在第一时间识别“它本质是个连续计数问题”。一旦识别出来,代码就该像上面这样干净,不绕。

写到这就够了。连续这两个字,看着简单,写错的人比你想的多。