Python技术迷

连续两个月裁员,果然没逃过。心情还行,因为我旁边的博士也被裁了

刚刷到这个,给我看得有点想笑,又有点心酸。

有网友说,公司这两个月一直在裁人,一波接一波,本来还想着自己是不是能躲过去,结果还是没跑掉。更绝的是,他心态居然还挺稳,因为坐他旁边那个博士也一起被裁了。

Image

这就很职场。你以为自己不够强才被优化,回头一看,学历高的、项目多的、平时看着很稳的,也照样被打包带走。那一瞬间人反而释怀了:哦,原来不是我菜,是公司真不做人。

打工人最怕的不是被裁,是被裁完还自我怀疑。结果旁边博士一走,直接给他完成心理疏导了。HR都不用安慰,工位旁边这位已经替公司把情绪价值给满上了。

算法题:字母移位

这题一眼看过去,最容易写成两层循环。

s = "abc",shifts = [3, 5, 9]。

意思是:

第 0 次,把前 1 个字母右移 3 次;

第 1 次,把前 2 个字母右移 5 次;

第 2 次,把前 3 个字母右移 9 次。

最后结果是:

abc
dbc
igc
rpl

如果直接按题意模拟,代码大概会写成这样:

defshift_slow(s, shifts):
    chars = list(s)

for end, step in enumerate(shifts):
        move = step % 26
for i in range(end + 1):
            old = ord(chars[i]) - ord('a')
            chars[i] = chr((old + move) % 26 + ord('a'))

return''.join(chars)

这段代码没毛病,但我第一眼就不太信它能过大数据。

因为它每次都在反复改前缀。

shifts 长度如果是 10 万,外层 10 万,内层平均 5 万,这就不是算法题了,这是给 CPU 烧香。

这题真正要看的不是“怎么移动字母”,而是“每个位置到底被移动了多少次”。

拿 abc 来看:

a 会受到 shifts[0] + shifts[1] + shifts[2]
b 会受到 shifts[1] + shifts[2]
c 会受到 shifts[2]

也就是说,每个位置受到的是它右边那一段的累加值。

所以不用从左往右模拟操作,反过来从右往左扫一遍就行。

扫的时候维护一个 total_shift,表示当前位置最终要右移多少次。

代码我一般会这样写:

defshifting_letters(s, shifts):
if len(s) != len(shifts):
raise ValueError("s 和 shifts 长度不一致,这种数据别继续算")

    chars = list(s)
    total_shift = 0
    base = ord('a')

for i in range(len(chars) - 1, -1, -1):
        total_shift = (total_shift + shifts[i]) % 26

        old_pos = ord(chars[i]) - base
        new_pos = (old_pos + total_shift) % 26
        chars[i] = chr(base + new_pos)

return''.join(chars)

跑一下刚才那个例子:

print(shifting_letters("abc", [3, 5, 9]))

输出:

rpl

这里有个小细节,total_shift 每次都要 % 26。

不是为了正确性才必须立刻取模,Python 大整数也能扛一阵。但没必要让它一直涨。字母一共就 26 个,右移 27 次和右移 1 次没区别。

再看一个边界:

print(shifting_letters("z", [1]))

输出:

a

这个地方如果忘了取模,或者字符位置算错,很容易把 z 移到 {,这类 bug 看着小,线上做字段脱敏、券码生成、简单编码时也真见过。

这题最后其实就一句话:别模拟每一次前缀移动,直接算每个字符最终被移动的总次数。

时间复杂度从 O(n²) 降到 O(n)。

空间上除了结果数组,没有额外大东西。

我不太建议这题上来就背“后缀和”三个字。先把每个字符被哪些操作影响写出来,规律自然就出来了。算法题有时候不是不会写,是一开始就顺着题意硬干,方向歪了。