程序员老鬼

某军工研究所网友爆料,一个月加班接近 20个小时,被同事警告说加班太少,他们都是60~70小时起步~

刚看到个贴子,说某军工研究所的网友一个月加班快 20 小时,结果被同事提醒“太少了”。还有网友补刀,说他们那边基本 60、70 小时起步。

Image

网友的回帖我看了看,很多人都在强调“我们更苦”,但比惨其实没意义。加班不是荣耀,更不是攀比的资本,说到底还是价值和回报要对应才正常。

换个角度想,职场里最怕的不是加班,而是加了班还被要求“情绪稳定”“懂奉献”。这种心态压力比工时更累。该努力努力,但也别把透支当常态。

对个人来说,搞钱不是丢脸,保护自己更不是错。【备注:文末可领最新资料】

面试题:最小区间

先把题目翻成大白话哈:

有 k 个升序数组(每个都是从小到大排好的),你要找一个区间 [L, R],要求这 k 个数组里每个数组至少有一个数落在这个区间里,而且这个区间要尽量短,也就是 R - L 尽量小,如果有多种答案,一般取左端点更小的那个就行。

你可以脑补成这样一个场景:

每个数组都是一条“有序流水线”,我们一开始把每条线的第一个数拿出来,放在桌子上。此时桌子上的这 k 个数里:

  • 最小值 = 当前区间的左端点候选
  • 最大值 = 当前区间的右端点候选

这不就刚好构成一个“覆盖所有数组”的区间了吗?因为桌子上每个数都来自不同的数组,各占一个名额。

那接下来怎么办?

直觉是: 当前区间的长度 = max - min, 要想再缩小,只能把当前最小的那个数往右挪一格,因为如果你挪别的数,有可能某个数组就“断供”了,不再被覆盖。

所以整体套路就出来了:

  1. 用一个小顶堆(优先队列)存这 k 个“当前指针所在的元素”,这样每次都能 O(log k) 拿到最小值。

  2. 额外用一个变量 curMax 记录当前桌面上的最大值。

  3. 每一轮:

  • 从堆里弹出最小值 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

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