在大厂工作10年左右被裁,现在接近45岁了,找工作真的太难,外包倒是有机会,40W左右,但后面怕是找不到正式的了。
刚看到个贴子,说一位在大厂干了十年的中年程序员被裁,快45岁了,只能拿到年薪四十万的外包机会,纠结要不要去,还想把这段经历当成求职空白期。
网友回帖有骂外包“是坑”,也有人劝他“先上岸,先保现金流”,还有人说“履历哪有饭碗重要”。
我觉得这事吧,关键不是面子,而是生存。这个年纪真空窗半年,一旦再遇到行情下行,压力会翻倍。外包也是工作经验,也是人脉和项目,只要不是乱跳、不是明显降维,简历上弱化处理就行,没必要刻意遮掩。
说到底,别总想着一步到位的“理想体制内岗位”。先稳住收入,再腾出精力补技能、扩圈子。路径可以曲折,但人得持续在线,这样下一次机会来时,你还有筹码去谈。
力
算法题:匹配子序列的单词数
想象你有一条很长很长的日志串 s,然后运营同学丢给你一堆关键词 words,问你:“这些词里,有多少个其实是从这条日志里按顺序挑几个字符,就能凑出来的?”——这就是经典算法题:匹配子序列的单词数。
给你:
一个字符串 s一个字符串数组 words
如果某个单词 w 是 s 的子序列,就算一次。最后返回这样的单词一共有多少个。
子序列的意思是:可以在 s 里删掉一些字符(也可以一个都不删),但不能打乱原来的相对顺序。 比如 s = "abcde":
"ace"是子序列(取 a c e)"aec"不是子序列(顺序乱了)
朴素想法:一个一个匹配,时间直接爆炸
最直接的做法就是:对 words 里的每个单词,拿两个指针在 s 和 word 上滑:
defis_subseq(s, word):
i = j = 0
while i < len(s) and j < len(word):
if s[i] == word[j]:
j += 1
i += 1
return j == len(word)
然后外面再套一层循环:
defnumMatchingSubseq(s, words):
ans = 0
for w in words:
if is_subseq(s, w):
ans += 1
return ans
逻辑很直观,但复杂度大概是:
对每个单词,要扫一遍 s,复杂度O(len(s))一共有 len(words)个单词
总体就成了:O(len(s) * len(words)),如果 s 长度 5 * 10^4,words 也有 5 * 10^4,这复杂度就挺难顶了。
换个思路:别老从头扫字符串
慢的根本原因是:**你不停地从头到尾重复扫 s**。
那如果我们只扫一遍 s,顺便把所有单词都“推进进度条”,事情是不是就好很多?
有一个很巧的做法,叫“按当前需要的字符分桶”:
可以这么理解: 每个单词都有一个“当前想匹配的下标”,刚开始都是 0,也就是都在等自己的第一个字符。 那我们就根据“它现在在等哪个字符”,把这些单词分到 26 个桶里(假设只有小写字母)。
整体流程是这样的:
先把所有单词按照“第 0 个字符”丢到对应桶里。 比如 word = "abc",在等 'a',就丢到桶 bucket['a']。
然后我们从左到右扫一遍
s,每看到一个字符c:
如果这个单词已经完全匹配完了,答案 +1 否则,看它接下来要等哪个字符,再丢回对应的桶里
把桶 bucket[c] 里当前所有在等
c的单词拿出来(注意要清空这个桶)对这些单词的“进度”往后挪一位:
最后统计到底有多少个单词完成了匹配。
整个过程中,s 只遍历了 1 遍,每个单词里的每个字符也只被处理了一次,所以复杂度大概是:
O(len(s) + 所有单词长度之和)
这个就很香了。
核心代码(Python 实现)
直接上完整代码,我们用“保存当前位置 + 原始单词”的方式来实现分桶:
from typing import List
defnumMatchingSubseq(s: str, words: List[str]) -> int:
# 只考虑小写字母的话,26 个桶
buckets = [[] for _ in range(26)]
# 先按“当前需要的字符”把所有单词分桶
for w in words:
# 空字符串按题目一般不会出现,这里简单跳过
ifnot w:
continue
first = ord(w[0]) - ord('a')
# (当前匹配到的位置, 单词本身)
buckets[first].append((0, w))
ans = 0
# 扫一遍 s,每个字符把对应桶里的“候选人”处理一轮
for ch in s:
idx = ord(ch) - ord('a')
# 当前这个字符对应的桶里,有一批单词在等它
# 注意要先取出来,再清空桶,不然会死循环
current_list = buckets[idx]
ifnot current_list:
continue
buckets[idx] = []
for pos, w in current_list:
pos += 1# 刚刚匹配到了 w[pos]
if pos == len(w):
# 这个单词已经完全匹配成功
ans += 1
else:
# 还没匹配完,看看它下一个需要的字符
next_ch = w[pos]
next_idx = ord(next_ch) - ord('a')
buckets[next_idx].append((pos, w))
return ans
if __name__ == "__main__":
s = "abcde"
words = ["a", "bb", "acd", "ace"]
print(numMatchingSubseq(s, words)) # 输出 3
可以稍微走一遍上面的例子:
初始分桶:
"a"、"acd"、"ace" 都在桶 'a' 里 "bb" 在桶 'b' 里 扫到
s[0] = 'a':"a" -> 匹配完成,答案 +1 "acd" -> 进度到 'c',丢到桶 'c' "ace" -> 进度到 'c',丢到桶 'c' 处理桶 'a':
扫到
s[2] = 'c'时,桶 'c' 里的 "acd" / "ace" 再往后推进……
最后得到答案 3,正好是 "a"、"acd"、"ace" 这三个。
整道题其实就两句话:
朴素做法:每个单词在 s上扫一遍,容易超时。优化做法:把所有单词根据“当前要匹配的字符”分桶,然后只扫一遍 s,顺路推进所有单词的匹配进度。
这个套路在别的题里也挺常见: 只要你发现“很多东西都要在同一个大串上反复走来走去”,就可以想办法:能不能我只走一遍大串,让这些东西自己排队挪位置?
这题用 Python 写一遍、再自己多打几个断点,基本就彻底吃透了。
-END-
我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html
虎哥作为一名老码农,整理了全网最全《python高级架构师资料合集》,总量高达650GB