程序员老鬼

面试上一个神仙公司,运维50岁了,其他研发也45多了。公司不裁员,只要好好干活就行。我打算进去了,今年35岁了,是不是进入有点早

刚看到个贴子,说有人面试到一家神仙公司,运维都50岁了,研发也四十多,一个个都稳得很。公司也不裁员,只要好好干就行。楼主35岁,担心进去是不是太早了。

Image

网友的回帖大多是羡慕:这样的公司现在不多了,能进去算赚到。我也认同这种说法,但还是得冷静想想——这类稳定型公司,核心竞争力是“长期主义”,他们看重的是踏实干活、稳定输出,而不是年纪大小。

换句话说,这不是养老院,这是成熟团队的正常年龄结构。

35岁进去一点都不早,甚至刚刚好。你既有经验,又能融入节奏,不像新人那样需要带,也不像太资深的那样薪资高得吓人。

更何况,能看见50岁的运维还在岗位上,本身就是这个公司的安全感来源。【备注:文末可领最新资料】

面试题:判断子序列

判断子序列这个题,说白了就是一句话:给你俩串,问短的那个是不是长的那个里“扣扣扣”能按顺序抠出来。

比如: s = "abc",t = "ahbgdc",那肯定是 true, 因为你可以在 t 里依次找到 a、b、c,虽然中间隔了别的字母,但顺序没乱。

一、题目到底在问啥?

一般题目长这样(Java 口味):

给定两个字符串 s 和 t,判断 s 是否为 t 的子序列。 你可以从 t 中删除一些字符(也可以一个不删),但不能打乱原顺序,看看能不能刚好得到 s。

再举几个例子:

  • s = "abc",t = "ahbgdc" → true
  • s = "axc",t = "ahbgdc" → false(t 里找不到按顺序来的 a、x、c)
  • s = ""(空串),t 随便啥 → 一律 true,因为空串是任何串的子序列

二、最常用的解法:双指针一趟扫完

这题其实特别适合“边走边看”的思路,用两个指针:

  • i 指向 s(短的那个,想要验证的子序列)
  • j 指向 t(长的那个,母串)

做法就像这样:

  1. 一开始 i = 0,j = 0
  2. 如果 s[i] == t[j],说明这个字符对上了,i、j 都往后挪一格
  3. 如果不相等,就先把 j 往后挪,继续在 t 里找下一个机会
  4. 最后如果能让 i 走到 s 的末尾(i == s.length),说明 s 的每个字符都按顺序在 t 里找到了,返回 true
  5. 否则返回 false

为什么这种写法靠谱?

  • j 是在 t 里从左到右走一遍,顺序保证了
  • 每次字符匹配上才挪 i,所以 s 里的字符也只能按顺序往前走
  • j 一直往前不会回退,所以时间复杂度就是 O(|s| + |t|),一遍过,巨省事

三、Java 代码长啥样?

就按上面说的思路翻译一下就行:

publicclassSubsequenceChecker{

// 判断 s 是否是 t 的子序列
publicstaticbooleanisSubsequence(String s, String t){
// 边界情况先处理下
if (s == null || t == null) {
returnfalse; // 一般面试里可以约定不传 null,这里稳一点
        }

int m = s.length();
int n = t.length();

// 空串一定是子序列
if (m == 0) {
returntrue;
        }

// 如果 s 比 t 还长,肯定不可能
if (m > n) {
returnfalse;
        }

int i = 0; // 指向 s
int j = 0; // 指向 t

while (i < m && j < n) {
if (s.charAt(i) == t.charAt(j)) {
                i++;
                j++;
            } else {
                j++; // 在 t 里继续往后找
            }
        }

return i == m;
    }

publicstaticvoidmain(String[] args){
        System.out.println(isSubsequence("abc", "ahbgdc"));   // true
        System.out.println(isSubsequence("axc", "ahbgdc"));   // false
        System.out.println(isSubsequence("", "anything"));    // true
    }
}

这就是最经典版本,面试官十有八九就想要你写这个。

四、几个容易忽略的小细节

不复杂,但有几个点面试时很容易被问到:

  1. 空串怎么处理?按定义,空串是任何字符串的子序列,所以直接返回 true。

  2. 大小写算不算一个字符?这得看题目,一般默认区分大小写,即 'a' != 'A'。 如果要不区分大小写,可以统一转小写:s = s.toLowerCase(); t = t.toLowerCase();

  3. 提早剪枝如果你在循环里发现 m - i > n - j(s 剩下的长度比 t 剩下的长度还长),那就直接 false,后面不用再找了,虽然节省的不多,但说明你想得挺细。

五、如果有很多个 s 要查怎么办?

有些进阶版本是这样的: 给你一个很长的 t,然后有成千上万个 s1、s2、s3…,问你每个是不是 t 的子序列。 如果你对每个 s 都从头到尾扫一遍 t,就有点浪费了。

这时候可以对 t 做个“预处理”,比如:

  • 为每个字符记录它在 t 中出现的所有位置列表
  • 对某个 s,从左到右一个字符一个字符地,用“上一位置之后第一次出现的位置”这个逻辑配合 二分查找 去找
  • 这样每个 s 的判定就变成:O(|s| * log |t|)

大概代码骨架像下面这样(只给个感觉):

// 预处理 t:字符 -> 递增索引列表
Map<Character, List<Integer>> posMap = new HashMap<>();
for (int i = 0; i < t.length(); i++) {
char c = t.charAt(i);
    posMap.computeIfAbsent(c, k -> new ArrayList<>()).add(i);
}

// 判断某个 s 是否是子序列
booleanisSubsequenceMany(String s){
int prevIndex = -1;
for (int k = 0; k < s.length(); k++) {
char c = s.charAt(k);
        List<Integer> list = posMap.get(c);
if (list == null) returnfalse;

// 在 list 里二分找 > prevIndex 的最小位置
int l = 0, r = list.size() - 1, ans = -1;
while (l <= r) {
int mid = (l + r) >>> 1;
if (list.get(mid) > prevIndex) {
                ans = list.get(mid);
                r = mid - 1;
            } else {
                l = mid + 1;
            }
        }
if (ans == -1) returnfalse;
        prevIndex = ans;
    }
returntrue;
}

单次调用看着比双指针复杂,但在“一个 t,很多个 s”的场景下,总体会快不少,这类写法在大厂面试题里也蛮常见的。

这题本身不难,亮点主要在:

  • 你能不能一开始就想到双指针,而不是暴力乱搞
  • 有没有考虑到空串、长度剪枝这类细节
  • 如果面试官顺着问“很多个 s 怎么办”,你能不能自然地说出“预处理 + 二分”这个升级版

整套下来,其实就是在检查你对“顺序扫描”“指针移动”和“时间复杂度”的基本功掌握得怎么样。只要多写几遍,基本就变成条件反射了。

-END-

我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html

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