程序员老鬼

跟一个年薪100万的技术总监吃饭,席间聊到年底绩效评级,他说决定谁拿A+、谁升职,只看一个指标,这人的“静音能力”。

刚看到个贴子:网友和年薪百万的技术总监吃饭,聊到年底绩效。总监说,谁拿A+谁升职,他只看一个指标——“静音能力”,不看加了多少班。听完的确有点发凉。

Image

网友回帖,有人夸这是情绪稳定,有人骂是培养“哑巴工具人”。我觉得,两边都没说到点上。

在我看来,这个“静音”,其实考的是情绪管理和边界感:能不能少抱怨、多解决问题,不在群里天天输出负能量,也不会一点不公就当场炸裂。但如果公司只爱安静、不鼓励合理发声,那迟早问题堆成雷。

总的来说吧,职场确实需要“静音能力”,但更需要“清晰表达”的能力。既能稳住情绪,又敢在关键时刻说真话的人,才是真正值得被重用的。

面试题:缺失的区间

我跟你说,缺失的区间这个题啊,真的是那种,看着不难,写起来容易翻车的那种。 我前两天在公司楼下喝咖啡,陪我们组那个小李刷题,他面试前临时抱佛脚,就把这道拿出来问我:“哥,这玩意儿到底咋优雅地写?”

题目大概意思就是:

给你一个有序、不重复的整型数组 nums,还有一个整体区间 [lower, upper],让你把在这个大区间里、但没出现在数组里的那些连续区间找出来,比如:

lower = 0, upper = 99nums = [0, 1, 3, 50, 75]

那缺的就是:2、4->49、51->74、76->99 这几段。 返回的时候一般用字符串表示,比如 "2"、"4->49" 这种。

当时小李第一反应是那种,暴力:从 lower 一路扫到 upper,用个 set 看每个数字在不在数组里。 我说你这思路当然也能做,就是——很费劲,数据大一点你就知道啥叫超时了,对吧。

比较舒服的写法,其实就一个思路:把“当前数字”变成“前一个端点”,遍历一遍数组顺带把空档区间捡出来。

我给他在纸上写了个小图: 想象一下我们在 [lower, upper] 外面,各往外再扩一个点:左边设成 lower - 1,右边设成 upper + 1,这俩是“哨兵”。 然后我们从左往右看:前一个数是 prev,当前看到的是 cur,只要 **cur - prev >= 2**,说明中间至少空了一个数,这一段就是缺失区间。

比如刚才那个例子:

  • 一开始 prev = lower - 1 = -1
  • 第一个数 cur = 0,差值 0 - (-1) = 1,没空档
  • 第二个数 cur = 1,1 - 0 = 1,也没空档
  • 第三个数 cur = 3,3 - 1 = 2,有空档,说明少了 [2,2],就是 "2"
  • ……
  • 数组扫完以后,再和 upper + 1 比一遍,把最后一段也补上

整件事就变成:用一个 prev 挂住“上一个存在的点”,每次看当前点和它之间有没有间隔。

小李当时一脸恍然:“原来连 for 循环都不用倒来倒去的那种?”

我顺手给他写了个 Java 版本,你可以直接拿去用,逻辑挺清爽的:

import java.util.ArrayList;
import java.util.List;

publicclassMissingRanges{

public List<String> findMissingRanges(int[] nums, int lower, int upper){
        List<String> res = new ArrayList<>();

// 用 long 是为了防止 lower = Integer.MIN_VALUE 时 lower - 1 溢出
long prev = (long) lower - 1;
long cur;

// 多加一个“虚拟元素”:upper + 1,方便统一处理最后一段
for (int i = 0; i <= nums.length; i++) {
if (i == nums.length) {
                cur = (long) upper + 1;
            } else {
                cur = nums[i];
            }

// 中间至少隔了一个数,说明有缺失区间
if (cur - prev >= 2) {
                res.add(formatRange(prev + 1, cur - 1));
            }

            prev = cur;
        }

return res;
    }

// 把 [start, end] 格式化成题目要的字符串形式
private String formatRange(long start, long end){
if (start == end) {
return String.valueOf(start);
        } else {
return start + "->" + end;
        }
    }

// 简单测一下
publicstaticvoidmain(String[] args){
        MissingRanges m = new MissingRanges();
int[] nums = {0, 1, 3, 50, 75};
        List<String> ans = m.findMissingRanges(nums, 0, 99);
        System.out.println(ans); // [2, 4->49, 51->74, 76->99]
    }
}

这个实现里有几个小细节,小李当时没注意到,我特地给他说了一遍,你也顺带看下:

第一个,那个 prev 我是用 long 存的,不是 int。为啥? 因为假如测试数据给你 lower = Integer.MIN_VALUE,你要是直接 lower - 1,就溢出了,结果一大堆奇怪的 bug,让人怀疑人生。 转成 long 就老老实实了。

第二个,把 upper + 1 当做“虚拟元素”塞到循环里。 这样你不需要在循环外再写一段“处理尾巴”的代码,否则你得先循环处理数组,再单独判断一下 upper 后面还有没有缺失,很啰嗦。 多加一个虚拟点,所有逻辑都长得一样,整个 for 写完直接收工。

第三个,formatRange 那里分成“单点”和“区间”两种情况:

  • 当起点等于终点,就说明只缺一个数,比如缺 2,就输出 "2";
  • 当不等,就输出 "start->end",比如 "4->49"。

面试官一般会看你这块边界处理是不是干净,不多空格、不乱写。

时间复杂度这题就很好说了:整个数组只过一遍,O(n),空间就是返回列表那点东西,额外 O(1)。 我当时跟小李说:这种题就是用一个变量把“之前的信息”记住,不要每次都从头算,这种思想比这道题本身更重要。

对了,那天聊完这个,我们又扯到以前写数据库和 MQ 那些踩坑故事,思路其实都差不多:一边看日志、一边盯关键“断点”,找中间缺的那一段,你看问题很多都是同一个味道,只是壳子不一样而已。

行,先这样,我去给小李催一下简历进度,他这两天又开始摸鱼了……

-END-

我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html

最后给大家分享一份不错的副业资料,点击下方公众号,回复关键字: 副业 领,也可以链接我微信:hls404