程序员老鬼

天塌了。。公司直接集体降薪,同事18K变10K~

真的是天塌了,有网友吐槽,自己公司搞了个骚操作:集体降薪,统一价10K。有同事原来18K的工资,一夜之间缩水快一半。关键是,活儿一点没少,还得照旧干。

Image

我看这不是降薪,这是职场PUA啊,典型的钱少事多还不能走的“温水煮青蛙”。有的公司,真是没钱裁员,就靠这套玩法逼你自己走人。你说他裁了你,还有个N+1;现在倒好,你不走就是接着干,走了还是一拍两散,零补偿💔。

你问我怎么看?讲真,我不怪老板没钱,我怪他玩阴的。如果真难,摊开讲,哪怕打个欠条也比这种“温柔暴力”让人心寒。

打工人不是怕苦,是怕被当傻子。你让我做牛做马没关系,别忘了草料啊。【备注:文末可领最新资料】

算法题:天际线问题

这道“天际线问题”,一看就不是实习生出的题,属于那种看起来跟图形学有关,其实是堆+扫描线结合的典范算法题。虽然它名字挺文艺,但你真写起来,会发现这玩意儿折磨得你脑壳疼 😵‍💫

题目描述大概是这样:给你一堆建筑,每个建筑是一段区间和一个高度,然后你得输出这些建筑堆起来之后,从远处看它的天际线轮廓。通俗点说,就是你得找出这些建筑在X轴上的“拐点”——每个拐点都是天际线变化的地方。

有点像什么?就像你站在一栋楼对面画剪影一样,哪些地方高了、哪些地方低了都要画出来...

好了废话少说,说点硬核的。常规思路你可能会想:是不是要先把这些建筑按左边界排序,再一栋一栋去模拟它的效果?别想太多,这么干肯定超时。因为你得不停地查最大高度、插入、删除,效率直接扑街。

真正靠谱的做法,是扫描线 + 最大堆(优先队列)。

思路是这样子的:

  1. 把每栋建筑拆成两个点:进入点 (left, -height) 和离开点 (right, height)。注意进入点高度是负的,方便等会儿排序的时候先处理高的。
  2. 把所有点按横坐标排序,如果横坐标相同,按高度排序。
  3. 扫描所有点,遇到进入点就把高度放进堆里,遇到离开点就把高度从堆里移除(这点很坑)。
  4. 每次扫描后,堆顶就是当前“天际线”的高度,跟上次不一样就记一笔。

代码不多,给你看下核心部分:

public List<List<Integer>> getSkyline(int[][] buildings) {
    List<int[]> points = new ArrayList<>();
for (int[] b : buildings) {
        points.add(newint[]{b[0], -b[2]}); // 进入点
        points.add(newint[]{b[1], b[2]});  // 离开点
    }

    points.sort((a, b) -> {
if (a[0] != b[0]) return a[0] - b[0]; // 横坐标升序
return a[1] - b[1]; // 高度升序(负数在前)
    });

    PriorityQueue<Integer> heap = new PriorityQueue<>((a, b) -> b - a); // 最大堆
    heap.add(0); // 地面高度
int prev = 0;
    List<List<Integer>> res = new ArrayList<>();

for (int[] p : points) {
if (p[1] < 0) {
            heap.add(-p[1]); // 进入
        } else {
            heap.remove(p[1]); // 离开(Java的堆移除不是O(logN),这里是个坑)
        }
int curr = heap.peek();
if (curr != prev) {
            res.add(Arrays.asList(p[0], curr));
            prev = curr;
        }
    }
return res;
}

这个 heap.remove() 是个小坑,不是对堆内部结构的直接删除,它是遍历堆找元素然后重排,最差O(n)。所以这个写法在数据量大时性能会崩,如果你面试碰上这题,记得装一波——“我知道用TreeMap优化掉这个remove的复杂度”😉

实际项目里呢?这种题不常见,但你如果搞前端图形引擎或者高性能游戏引擎,扫描线算法可是老本行;还有做GIS系统或建筑规划模拟的后端也会碰见类似问题。

说回堆这个结构,它在Java里没什么存在感,大家用得最多的其实是PriorityQueue,但那玩意儿默认是最小堆,要搞最大堆得自定义Comparator,不然你一不小心就调成升序了,然后一通debug调到头秃...

另外说个冷知识:这题在LeetCode上是Hard级别,但其实它的核心套路在很多中等题里都能练,比如“合并区间”、“会议室安排”、“找最大重叠区间”等等,都是类似的处理。

写这题的时候我一开始也想简单了,想用一个Set存活跃的高度集合,但一旦遇到多个建筑有相同起点或终点,输出的拐点就不对了。最后还是老老实实地上堆,顺便复习一下TreeMap那点骚操作(TreeMap可以维护元素个数,不用担心堆里有重复)。

有兴趣的可以试试写个版本是用TreeMap替代堆的,看你性能能不能跑满;不过别指望生产环境真用这个解天际线,除非你公司是搞建筑仿真的 😅

对了,面试官如果追问你为啥负高度能解决排序优先级的问题,一定要解释清楚——负数让进入点优先排序,然后高的在前,这样才能保证天际线的“山头”不会被低的遮住。

下次你再看到一堆建筑从左往右排过去,不妨用脑子描一下这天际线,你会发现算法其实也挺浪漫的 🌆

最后,我为大家打造了一份deepseek的入门到精通教程,完全免费:https://www.songshuhezi.com/deepseek

也可以看我写的这篇文章《DeepSeek满血复活,直接起飞!》来进行本地搭建。

-END-

ok,今天先说到这,老规矩,给大家分享一份不错的副业资料,感兴趣的同学可以链接我,微信:hls404 找我领取。

以上,就是今天的分享了,看完文章记得右下角点赞,也欢迎在评论区写下你的留言。