程序员老鬼

36被裁 找工作无望了 过了 35就是死刑 自寻出路了华5本硕 太难了 一副好牌被我打得稀烂感

刚刷到这个,心里真有点堵。

36岁,华五本硕,放哪看都不算差,结果一被裁,投出去的简历跟扔海里一样。以前总觉得“35岁危机”是网上吓唬人,真轮到自己,才发现很多公司连聊都懒得聊,先看年龄,再看你能不能加班,能力反而排后面。

Image

最难受的还不是没工作,是你回头一看,学历有了,班也没少加,年轻时候觉得再混几年就稳了。现在公司一句优化,人直接被清场。

说什么一副好牌打烂了,其实很多打工人哪有出牌的机会,牌桌都是别人掀的。36岁又不算老,可招聘软件上那道线,画得跟判决书似的,看得人真窝火。

今日面试题

最长和谐子序列,别一上来就写双指针

数组 [1,3,2,2,5,2,3,7] 丢进来,答案是 5。

取出的子序列是 [3,2,2,2,3],最大值为 3,最小值为 2,两者相差正好等于 1。

这道题看着像滑动窗口,我第一眼也会想到排序加双指针。但再看一遍题目,会发现“子序列”不要求元素连续,原数组里的顺序也不影响最终长度。既然只关心数字出现了多少次,排序就显得有点重了。

题目的关键限制只有一个:子序列中的最大值和最小值必须相差 恰好为 1。

这里有个容易写错的地方。数组 [2,2,2,2] 不能返回 4,因为最大值和最小值相差 0,不是 1。看到四个相同数字就直接累加,这种代码测试样例少的时候还真不容易暴露。

处理方式很直接:先用 HashMap 统计每个数字出现的次数。假设当前数字是 x,只要数组中还存在 x + 1,那么这两个数字就能组成一个和谐子序列,长度就是它们出现次数之和。

代码不用写得太绕:

import java.util.HashMap;
import java.util.Map;

publicclassHarmoniousSequence{

publicintfindLongest(int[] nums){
if (nums == null || nums.length < 2) {
return0;
        }

        Map<Integer, Integer> frequency = new HashMap<>();

for (int value : nums) {
            frequency.merge(value, 1, Integer::sum);
        }

int longest = 0;

for (Map.Entry<Integer, Integer> item : frequency.entrySet()) {
int current = item.getKey();

// 防止 current + 1 在极端情况下发生整数溢出
if (current == Integer.MAX_VALUE) {
continue;
            }

            Integer nextCount = frequency.get(current + 1);
if (nextCount == null) {
continue;
            }

int candidate = item.getValue() + nextCount;
            longest = Math.max(longest, candidate);
        }

return longest;
    }
}

拿开头的数组跑一下,频次大致是:

1 -> 1
2 -> 3
3 -> 2
5 -> 1
7 -> 1

遍历到 1 时,可以和 2 组合,长度是 4。

遍历到 2 时,可以和 3 组合,长度是 5。

遍历到 3 时,找不到 4,直接跳过。后面的 5 和 7 也没有相邻数字,最终结果就是 5。

这段代码只扫描了一次数组,又遍历了一次频次表,时间复杂度是 O(n),额外空间复杂度也是 O(n)。

当然,排序后用双指针也能做,时间复杂度是 O(n log n)。但这题没有区间连续性的要求,只为统计两个相邻数值的数量去排序,我一般不会这么写。能用频次表把问题压成一次查找,就没必要先把整个数组重新排一遍。