Python技术迷

同事工资 2.5w,外企苟了9年,今年35岁,本来已经准备被裁,开始面试。结果一直裁不到,反而拿到阿里offer涨薪50%...

刚看到个贴子,说一位同事在外企苟了9年,35岁,本来以为随时会被裁,结果裁员风暴一直轮不到他。正迷茫准备跳槽时,又被阿里给了个涨薪50%的 offer,现在反而纠结得不行。

Image

怎么说呢,纠结是正常的,毕竟外企稳定、阿里钱多,各有利弊。但我更认同一个观点:35岁能拿到更好的机会,就别被“舒适区”困住。职场像坐地铁,车门开了,你不上,总有人会上;机会来了,不抓住以后可能就没了。

不过话说回来,最终还是要看他追求啥——要安全感就继续待外企,要收入和上升空间就去阿里。选择没有绝对对错,只要能让自己未来更踏实就行。

总的来说,35岁还能被市场认可,本身就是积极信号。走哪条路不重要,重要的是别停下,让自己保持价值。【备注:文末可领最新资料】

面试题:最短回文串

昨天晚上快十一点,我在公司楼下等外卖的时候刷题,就刷到这个“最短回文串”,当时脑子一懵:这不就是给你个字符串,让你在前面尽量少加点字符,把它变成回文嘛,看着简单,其实还挺能拷打人思维的。

题目意思大概就是:给你一个字符串 s,你只能在“前面”加字符(后面不能动),让整个字符串变成回文串,而且要最短。比如:

  • aacecaaa → 最短答案是 aaacecaaa(前面加个 a)
  • abcd → 最短答案是 dcbabcd(前面加 dcb)

注意两个点哈: 只能在前面加; 想要加得尽量少,本质就是——尽量保留原来前面那一大段。

直觉版思路:暴力但好理解

最土的方法其实挺容易想的,就是那个…你从右往左找,看看前面这一段是不是回文:

  • 先看整个 s 是不是回文,是就直接返回;
  • 不是,就砍掉最后一个字符,看看 s[0:len-1] 是不是回文;
  • 再不是,就砍两个,s[0:len-2],一直砍到只剩一个字符。

你想啊,只要找到了“最长的回文前缀”,后面那一段不是回文的部分,反过来塞到前面就好了。

比如 abcd: 最长回文前缀只有 a,剩下 bcd,反过来 dcb 加前面,就变成 dcbabcd。

用 Python 写就是这样,很朴素:

defis_pal(s: str) -> bool:
return s == s[::-1]

defshortest_palindrome_slow(s: str) -> str:
ifnot s:
return s
# 从最长前缀开始找
for i in range(len(s), -1, -1):
if is_pal(s[:i]):
            suffix = s[i:]
return suffix[::-1] + s

这个写法面试要是数据量小,其实也能过,但时间复杂度大概是 O(n²),字符串很长就有点顶不住了。

关键想法:我要的只是“最长回文前缀”

刚才其实已经说到核心了: 整题就是一句话——找到 s 的“最长回文前缀”的长度 L,然后把 s[L:] 反转加到前面。

那问题就变成:怎么样又快又准地找到这个 L?

有一个挺经典的小技巧,跟 KMP 里那个前缀函数有点像,但不用你死背公式。

我们造一个新串:

t = s + '#' + reversed(s)

为啥中间要加 #?就是随便塞个不可能在原字符串里出现的分隔符,避免串自己乱匹配。

然后对这个 t 求“最长相等前后缀”的长度数组(俗称前缀函数 pi)。 最后一个位置的 pi[-1],刚好就是原串 s 的“最长回文前缀”的长度。这个结论你先记住,直觉理解一下就够用:前面是 s,后面是 rev(s),能对齐的最长前后缀,正好就是“前面这段 = 后面从尾巴反过来的那段”,也就是“回文前缀”。

KMP 前缀函数怎么写?别怕,代码挺顺眼的

defshortest_palindrome(s: str) -> str:
ifnot s:
return s

    rev = s[::-1]
    t = s + '#' + rev

# 计算前缀函数 pi 数组
    pi = [0] * len(t)
for i in range(1, len(t)):
        j = pi[i - 1]
while j > 0and t[i] != t[j]:
            j = pi[j - 1]
if t[i] == t[j]:
            j += 1
        pi[i] = j

# pi[-1] 就是最长回文前缀的长度
    longest_prefix = pi[-1]
    suffix = s[longest_prefix:]
return suffix[::-1] + s

你大概顺着看一遍:

  • pi[i] 的意思:以 i 结尾的子串里,“前缀 == 后缀”的最大长度;
  • 循环里 while 那段,就是不匹配就“往前跳”,复用之前算好的结果;
  • 最后一个 pi[-1],对应的就是整个 t 的“最长前后缀”,也就是我们要的东西。

随便带个例子走一遍:

  • s = "aacecaaa"
  • rev = "aaacecaa"
  • t = "aacecaaa#aaacecaa"算出来 pi[-1] = 7,说明前面有个长度为 7 的回文前缀,也就是 aacecaa,剩下一个 a 在后面。 那我们就把这个后面的 a 反转加前面 → a + aacecaaa = aaacecaaa,完事。

整个算法时间复杂度 O(n),空间 O(n),面试官一般会挺满意。

如果一时想不起 KMP,还有个“二刷版”写法

有时候脑子糊了,KMP 细节老记不住,其实还有一个写法也挺常见,就是用双指针,从头尾一起往中间扫,只不过会稍微偏暴力一点,但可以写得还行。

大概意思是:

  • 先用两个指针 i 和 j 指向头和尾;
  • 如果 s[i] == s[j] 就一起往里缩;
  • 不等的话,就暂时“丢弃”右边这个尾字符,让 j -= 1,并记录有多少个不匹配的尾巴;
  • 最后根据这个“尾巴长度”,把那一截反过来加到前面。

这种写法实现起来也不长,不过容易写出 O(n²) 的版本,就不展开了,真正想追求性能还是老老实实用上面那个前缀函数版。

这个题其实挺典型的那种——你硬上暴力肯定能写,但想优雅一点就要借“字符串匹配”的现成工具。 以后刷到类似“最短 XXX”、“只在一边加东西”的题,脑子里可以先冒一句:是不是在找什么“最长前缀 / 后缀”的结构,然后再想想 KMP、哈希这些老朋友。

行了,这个题差不多就这样,我去给自己冲杯咖啡,你要是想顺带写下测试用例或者扩展成处理只包含字母数字的情况,也是几行代码的事。

-END-

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

🔥虎哥私藏精品🔥

虎哥作为一名老码农,整理了全网最全《python高级架构师资料合集》,总量高达650GB