同事月薪2万8,上有老下有小,房贷每月1万2。他跟我说:我现在出去面试,人家一看年龄就摇头。
刚看到个贴子:有网友说,他那位38岁的老同事,被HR叫去谈话,直接按N+3请走。人月薪2万8,房贷1万2,上有老下有小,现在出去面试,一看年龄对方就摇头。
这事真不能简单怪哪一方。公司看的是成本和即战力,人到中年,只能为自己的兜底能力买单。
在我看来,中年最大的安全感,从来都不是现在这份工作,而是你随时再找一份工作的能力:手上的技能、人脉、存款、备选方向。趁风平浪静时多练几招、少背点债,比转发一万篇鸡汤都管用。工作会变,但只要人还在成长,路就不会一下走死。
面试题:最长回文子串
回文你肯定知道吧,就是正着读反着读都一样那种,aba、abba、a。
题目大概就是:给你一个字符串,比如 "babad",让你在里面找一个最长的回文子串出来。 像 "bab" 和 "aba" 都行,随便返回一个就可以。
重点有两个:
必须是连续的子串,不是子序列 要最长,不是随便找一个就跑
面试官一般会补一句:字符串长度可能是几千、几万,你可别给我整个 O(n³) 的暴力。
小李第一反应一般都是这种思路: “把所有子串枚举一遍,每个子串再检查是不是回文,取最长的那个。”
听着没问题对吧,伪代码脑补一下:
左边界 i 从 0 到 n-1 右边界 j 从 i 到 n-1 然后再用一个 while 从两边往中间比字符
时间复杂度怎么算呢: 枚举子串是 O(n²),每个子串再检查一次 O(n),合起来 O(n³)。 n 如果是 1000,立马炸裂,面试官人都没了。
所以这个思路一般只能当你跟别人说:“我先说个最直观但会超时的做法哈。” 说完赶紧自己把自己否了,再换个版本。
我当时就跟小李说,你别老想着“枚举子串”,你可以换个角度:“枚举回文的中心”。
为啥?因为回文这个东西有个很爽的性质:它是以中点对称的。
你看这几个:
奇数长度: aba以中间的b为中心偶数长度: abba以中间那两个b之间缝为中心
所以我们可以干一件事:
枚举每一个“中心位置” 从这个中心往两边扩展,只要两边字符一样就继续扩 扩不动了,这一轮就拿到了一个以它为中心的最长回文
注意中心有两种情况:
1 个字符当中心:处理奇数长度,比如 aba2 个字符当中心:处理偶数长度,比如 abba
所以第 i 个位置,要算两次:
以 (i, i)为中心以 (i, i+1)为中心
每次扩展的那段长度算出来,跟全局的最长做对比,更新一下起始下标就完了。
时间复杂度怎么说呢: 中心有 n 个,每个中心最多向两边扩 n 次,整体就是 O(n²),面试官一般是能接受的。 空间只用常数几个变量,O(1)。
我抽完最后一口烟,给小李在手机上敲了个最常用的版本,大概就这样:
publicclassLongestPalindrome{
public String longestPalindrome(String s){
if (s == null || s.length() < 2) {
return s;
}
int start = 0;
int end = 0;
for (int i = 0; i < s.length(); i++) {
// 情况一:奇数长度,以 i 为中心
int len1 = expandFromCenter(s, i, i);
// 情况二:偶数长度,以 i 和 i+1 为中心
int len2 = expandFromCenter(s, i, i + 1);
int len = Math.max(len1, len2);
// 当前找到的比历史最长的还长,就更新一下左右边界
if (len > end - start + 1) {
// 这个推导你可以自己拿纸算一下,很常见的写法
start = i - (len - 1) / 2;
end = i + len / 2;
}
}
return s.substring(start, end + 1);
}
// 从 left 和 right 出发,向两边扩展,返回以它们为中心的最长回文长度
privateintexpandFromCenter(String s, int left, int right){
int n = s.length();
while (left >= 0 && right < n && s.charAt(left) == s.charAt(right)) {
left--;
right++;
}
// 跳出循环的时候,left 和 right 已经多走了一步,所以要减 1
return right - left - 1;
}
// 简单测一下
publicstaticvoidmain(String[] args){
LongestPalindrome solver = new LongestPalindrome();
System.out.println(solver.longestPalindrome("babad")); // bab 或 aba
System.out.println(solver.longestPalindrome("cbbd")); // bb
System.out.println(solver.longestPalindrome("a")); // a
System.out.println(solver.longestPalindrome("ac")); // a 或 c
}
}
几个小点随手说下:
那两个公式 start = i - (len - 1) / 2和end = i + len / 2,其实已经把奇数和偶数长度统一处理掉了,不用单独分类expandFromCenter那个 while 出来以后,要返回right - left - 1,因为 left/right 都已经越界了一步,这是面试时最容易写错的地方如果字符串本身就很短,比如长度 0 或 1,直接返回就行,别瞎算
那会儿小李还不满足,又问:“那 dp 怎么搞?” 我困得不行就简单跟他讲了一句:
你可以开个二维数组 dp[i][j],表示子串 s[i..j] 是不是回文。 状态转移大概是:
两端字符相等: s[i] == s[j]并且里面那一段也是回文: dp[i+1][j-1] == true或者中间已经“空了”( j - i <= 2),比如aa、aba这种直接就是回文
然后 i 从后往前遍历,j 从 i 往后遍历,一边填表一边顺手记录最长长度和起点。 时间 O(n²),空间 O(n²),就不写代码了,面试的时候一般中心扩展那个版本够用,也更好写。
-END-
我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html