某军工研究所网友爆料,一个月加班接近 20个小时,被同事警告说加班太少,他们都是60~70小时起步~
刚看到个贴子,说某军工研究所的网友一个月加班快 20 小时,结果被同事提醒“太少了”。还有网友补刀,说他们那边基本 60、70 小时起步。
网友的回帖我看了看,很多人都在强调“我们更苦”,但比惨其实没意义。加班不是荣耀,更不是攀比的资本,说到底还是价值和回报要对应才正常。
换个角度想,职场里最怕的不是加班,而是加了班还被要求“情绪稳定”“懂奉献”。这种心态压力比工时更累。该努力努力,但也别把透支当常态。
对个人来说,搞钱不是丢脸,保护自己更不是错。【备注:文末可领最新资料】
面试题:最小区间
先把题目翻成大白话哈:
有 k 个升序数组(每个都是从小到大排好的),你要找一个区间 [L, R],要求这 k 个数组里每个数组至少有一个数落在这个区间里,而且这个区间要尽量短,也就是 R - L 尽量小,如果有多种答案,一般取左端点更小的那个就行。
你可以脑补成这样一个场景:
每个数组都是一条“有序流水线”,我们一开始把每条线的第一个数拿出来,放在桌子上。此时桌子上的这 k 个数里:
最小值 = 当前区间的左端点候选 最大值 = 当前区间的右端点候选
这不就刚好构成一个“覆盖所有数组”的区间了吗?因为桌子上每个数都来自不同的数组,各占一个名额。
那接下来怎么办?
直觉是: 当前区间的长度 = max - min, 要想再缩小,只能把当前最小的那个数往右挪一格,因为如果你挪别的数,有可能某个数组就“断供”了,不再被覆盖。
所以整体套路就出来了:
用一个小顶堆(优先队列)存这
k个“当前指针所在的元素”,这样每次都能 O(log k) 拿到最小值。额外用一个变量
curMax记录当前桌面上的最大值。每一轮:
从堆里弹出最小值 min。用 [min, curMax]去更新答案区间。把这个 min来自的那个数组的下一个元素拿出来,放回堆里,并顺手更新一下curMax。
如果某个数组已经用完了(这个数组没有“下一个元素”了),那游戏就结束,因为再往后移动就不能保证每个数组都被覆盖了。
时间复杂度大概是: 所有元素一共遍历一遍,每次进出堆是 log k,所以是 O(N log k),N 是总元素个数。
Java 代码实现(优先队列做多路归并)
下面是一个比较常见、也比较好理解的写法:
import java.util.*;
publicclassSolution{
// 一个小结构体,记录当前数值、来自哪个数组、在该数组中的下标
staticclassNode{
int value;
int listIndex;
int indexInList;
Node(int value, int listIndex, int indexInList) {
this.value = value;
this.listIndex = listIndex;
this.indexInList = indexInList;
}
}
publicint[] smallestRange(List<List<Integer>> nums) {
int k = nums.size();
// 小顶堆,按 value 从小到大排
PriorityQueue<Node> pq = new PriorityQueue<>(
(a, b) -> Integer.compare(a.value, b.value)
);
int curMax = Integer.MIN_VALUE;
// 初始化:每个数组先取第一个元素放进堆里
for (int i = 0; i < k; i++) {
int v = nums.get(i).get(0);
pq.offer(new Node(v, i, 0));
curMax = Math.max(curMax, v);
}
// 记录答案区间
int bestStart = 0;
int bestEnd = Integer.MAX_VALUE;
// 只要堆里还能保证有 k 个元素,就说明每个数组都有当前指针
while (pq.size() == k) {
Node curMinNode = pq.poll(); // 当前最小值
int curMin = curMinNode.value;
// 尝试更新答案
if (curMax - curMin < bestEnd - bestStart
|| (curMax - curMin == bestEnd - bestStart && curMin < bestStart)) {
bestStart = curMin;
bestEnd = curMax;
}
// 准备把这个最小值所在数组的下一个元素加入进来
int listIdx = curMinNode.listIndex;
int nextIdx = curMinNode.indexInList + 1;
if (nextIdx >= nums.get(listIdx).size()) {
// 这个数组已经用完了,没法再保证覆盖所有数组,结束
break;
}
int nextVal = nums.get(listIdx).get(nextIdx);
pq.offer(new Node(nextVal, listIdx, nextIdx));
// 更新当前最大值
if (nextVal > curMax) {
curMax = nextVal;
}
}
returnnewint[]{bestStart, bestEnd};
}
// 随便写个 main 做个简单示例
publicstaticvoidmain(String[] args){
List<List<Integer>> nums = new ArrayList<>();
nums.add(Arrays.asList(4, 10, 15, 24, 26));
nums.add(Arrays.asList(0, 9, 12, 20));
nums.add(Arrays.asList(5, 18, 22, 30));
Solution s = new Solution();
int[] ans = s.smallestRange(nums);
System.out.println("[" + ans[0] + ", " + ans[1] + "]");
}
}
再简单帮你捋一遍
这题本质就是“多路归并 + 滑动窗口”的结合体。 小顶堆负责每次找到当前窗口里的最小值; curMax负责记住窗口里的最大值;每次把“最小的那个往右挪一步”,看看能不能得到更短的区间。
理解了这个“桌子上永远放着每个数组的一个当前元素”的画面,这题就差不多拿下了。 如果你之后刷到“合并 K 个有序链表”,会发现套路几乎是一样的,就是换了个问法而已。
-END-
我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html