Python技术迷

某老板:你的工资是5到6千,不是5千到6千~

刚刷到这个,真给我看懵了。

员工入职前听老板说工资“五到六千”,正常人谁听了不理解成五千到六千?结果发工资一看,三千。去问老板,人家还挺淡定,说我核实了,三千没错,当初跟你讲的是“五到六千”,不是“五千到六千”。

Image

这话一出来,打工人血压都上来了。不是,这差别在哪?搁这儿玩文字密室逃脱呢?工资能这么解释,那以后“周末双休”是不是也能说成“双休是双倍休息幻想”?“包吃住”是不是变成“包含吃住这个词”?

最离谱的是,老板还一副自己很严谨的样子。好像不是他坑,是你语文学得太认真了。

这种操作真的挺牛马的,合同不写清楚,口头画饼,发钱开始抠字眼。HR看了都得沉默两秒:原来还能这么赖啊。

今日算法题

字符串 abab 一眼能看出来是重复的,aba 就不行。

但代码不能靠眼睛。这个题卡人的地方不在“会不会枚举”,而在你怎么判断一个字符串是不是由某个更短的片段拼出来的。

比如:

"abab"     = "ab" + "ab"
"abcabc"   = "abc" + "abc"
"aaaa"     = "a" + "a" + "a" + "a"
"abac"     不行

我第一反应一般不枚举所有子串长度。能过,但有点笨。尤其字符串长一点时,你会发现自己在不停切片、拼接、比较,写出来像在硬搬砖。

这个题有个很顺手的判断:看 KMP 里的 next 数组,也就是前缀表。

前缀表记录什么?

记录当前位置之前,字符串的“最长相同前后缀”长度。

拿 ababab 看:

a b a b a b
0 0 1 2 3 4

最后一个值是 4,说明整个字符串长度 6 里面,前后有 4 个字符能对上。

那剩下的长度就是:

6 - 4 = 2

这个 2 就是可能重复的最小周期长度。只要总长度能被它整除,就说明可以重复拼出来。

看代码:

classSolution:
defrepeatedSubstringPattern(self, s: str) -> bool:
        n = len(s)
if n < 2:
returnFalse

        lps = [0] * n
        left = 0

for right in range(1, n):
while left > 0and s[right] != s[left]:
                left = lps[left - 1]

if s[right] == s[left]:
                left += 1
                lps[right] = left

        same_part = lps[-1]
if same_part == 0:
returnFalse

        block_len = n - same_part
return n % block_len == 0

这里我比较在意这句:

left = lps[left - 1]

很多人第一次写 KMP,错就错在这里。匹配不上时,不是把 left 粗暴归零,而是退回到上一个还能接着匹配的位置。

否则你前面算出来的信息全浪费了。

跑几个例子:

cases = ["abab", "aba", "abcabcabc", "aaaa", "abac"]

for x in cases:
    print(x, Solution().repeatedSubstringPattern(x))

输出大概是:

abab True
aba False
abcabcabc True
aaaa True
abac False

还有一种很短的写法:

defcheck(s: str) -> bool:
return s in (s + s)[1:-1]

这写法面试里经常能看到,也确实巧。

比如 abab 拼两次变成:

abababab

去掉头尾:

bababa

里面还能找到原字符串 abab,说明它有周期。

但我一般不会只写这个。太像背答案了。面试官多问一句“为什么”,你还得绕回周期解释。

KMP 这版虽然代码长一点,但它把判断过程摊开了:先找最长相同前后缀,再算周期长度,最后看能不能整除。

这个题真正要防的不是语法,是误判。

ababa 的前后缀也能对上,但它不能由重复子串拼出来,因为长度 5 没法被周期 2 整除。

所以最后这个判断不能少:

return n % block_len == 0

少了它,代码看着像过了,实际一测边界就漏。算法题里这种“看起来差不多”的地方,通常就是坑。