Python技术迷

在大厂工作10年左右被裁,现在接近45岁了,找工作真的太难,外包倒是有机会,40W左右,但后面怕是找不到正式的了。

刚看到个贴子,说一位在大厂干了十年的中年程序员被裁,快45岁了,只能拿到年薪四十万的外包机会,纠结要不要去,还想把这段经历当成求职空白期。

Image

网友回帖有骂外包“是坑”,也有人劝他“先上岸,先保现金流”,还有人说“履历哪有饭碗重要”。

我觉得这事吧,关键不是面子,而是生存。这个年纪真空窗半年,一旦再遇到行情下行,压力会翻倍。外包也是工作经验,也是人脉和项目,只要不是乱跳、不是明显降维,简历上弱化处理就行,没必要刻意遮掩。

说到底,别总想着一步到位的“理想体制内岗位”。先稳住收入,再腾出精力补技能、扩圈子。路径可以曲折,但人得持续在线,这样下一次机会来时,你还有筹码去谈。

力

算法题:匹配子序列的单词数

想象你有一条很长很长的日志串 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 个桶里(假设只有小写字母)。

整体流程是这样的:

  1. 先把所有单词按照“第 0 个字符”丢到对应桶里。 比如 word = "abc",在等 'a',就丢到桶 bucket['a']。

  2. 然后我们从左到右扫一遍 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