现在还张口就要涨薪30%的人,多少有点活在上个版本了。大厂这两年招人,姿态都变了,坑少了,简历多了,面试官都趾高气昂的“你爱来不来”
有网友说得挺直接:现在能涨10%到15%,已经算谈得不错。再碰上业务线缺人、你手里真有活,20%差不多到头了。再往上抬?HR看完估计先把你归到“再议”那一栏。
更现实的是,履历一旦有点飘,稳定性一般,或者空窗拖太久,市场立马就不跟你讲情怀了。以前是你挑机会,现在很多时候是机会挑你。嘴上都说看长期发展,真到发 offer,那一行数字比什么都诚实。
所以现在跳槽,别先拿几年前的行情给自己壮胆。先看自己手里有没有硬货,再看市场认不认。
算法题:滑动窗口中位数
数组一长,这题就容易写飘。
“滑动窗口中位数”麻烦的地方,不是求中位数本身,而是窗口一直在滑:左边元素要删,右边元素要进。你要是每次都把窗口里的数重新排个序,那代码能过小数据,稍微大一点就开始喘,复杂度直接顶到 (O(nk\log k))。
这种题我第一眼一般不会碰排序重建,先想两件事: 一是怎么随时拿到中位数。 二是怎么把“删除窗口左边那个旧值”处理干净。
比较稳的做法,是两个堆。
一个大顶堆存左半边,堆顶是左边最大值。 一个小顶堆存右半边,堆顶是右边最小值。 这样一来:
难点在删除。Java 的 PriorityQueue 直接删任意元素不划算,所以这里别硬删,走“延迟删除”更顺手:先用 Map<Integer, Integer> 记下来某个值该删几次,等它刚好出现在堆顶,再真正弹掉。
核心思路其实就这几步:
classDualHeap{private PriorityQueue<Integer> small = new PriorityQueue<>((a, b) -> b - a); // 左边,大顶堆private PriorityQueue<Integer> large = new PriorityQueue<>(); // 右边,小顶堆private Map<Integer, Integer> delayed = new HashMap<>();privateint smallSize;privateint largeSize;privatefinalint k;publicDualHeap(int k){this.k = k; }publicvoidadd(int num){if (small.isEmpty() || num <= small.peek()) { small.offer(num); smallSize++; } else { large.offer(num); largeSize++; } balance(); }publicvoidremove(int num){ delayed.put(num, delayed.getOrDefault(num, 0) + 1);if (num <= small.peek()) { smallSize--;if (num == small.peek()) { prune(small); } } else { largeSize--;if (!large.isEmpty() && num == large.peek()) { prune(large); } } balance(); }publicdoublemedian(){if ((k & 1) == 1) {return small.peek(); }return ((double) small.peek() + large.peek()) / 2.0; }privatevoidbalance(){if (smallSize > largeSize + 1) { large.offer(small.poll()); smallSize--; largeSize++; prune(small); } elseif (smallSize < largeSize) { small.offer(large.poll()); smallSize++; largeSize--; prune(large); } }privatevoidprune(PriorityQueue<Integer> heap){while (!heap.isEmpty()) {int num = heap.peek(); Integer cnt = delayed.get(num);if (cnt == null || cnt == 0) {break; } heap.poll();if (cnt == 1) { delayed.remove(num); } else { delayed.put(num, cnt - 1); } } }}
主流程就很直了,先把前 k 个数塞进去,后面每滑一步,先加新值,再删旧值,顺手取中位数:
publicdouble[] medianSlidingWindow(int[] nums, int k) { DualHeap dh = new DualHeap(k);double[] ans = newdouble[nums.length - k + 1];for (int i = 0; i < k; i++) { dh.add(nums[i]); } ans[0] = dh.median();for (int i = k; i < nums.length; i++) { dh.add(nums[i]); dh.remove(nums[i - k]); ans[i - k + 1] = dh.median(); }return ans;}
这题真正容易错的地方有两个。
一个是平衡堆大小时,别只顾着搬元素,不清理堆顶脏数据。 另一个是偶数窗口求平均,别写成 int 相加再除,数据一大就不对了,直接转 double。
整体复杂度是 (O(n \log k))。这就像线上处理实时数据流,不会每次全量重算,只维护一个能动态进出、还能随时取中位数的结构。思路顺了,这题其实不难,难的是删除这一下,第一次写基本都会在这里卡一会儿。