Python技术迷

心动厂员工爆料,加班后在工位很晕,天旋地转,出公司差点摔倒,回家一量血压高达150!

刚看到个贴子,说字节有同学加班后当场天旋地转,出门差点摔倒,回家一量血压150,真挺吓人的。加班嘛大家都经历过,但能把人逼到这种程度,就不是“忙一点”的问题了,是身体在发警报了。

Image

我看网友回帖,有说大厂强度正常的,也有嘲讽“早就习惯了”的。怎么说呢,这种正常化高强度,其实最可怕。就像天天坐地铁挤成沙丁鱼,挤久了你会觉得理所当然,但身体不会帮你装糊涂。

从我的角度看,问题的关键不是加不加班,而是有没有底线、有没有换命式的消耗。血压150可不是小事,那已经是身体在拉闸断电了。工作再重要,也不值当把自己卷进医院。

不过话说回来,不是让大家躺平,但节奏真得自己把握。命是自己的,老板不会替你住院。【备注:文末可领最新资料】

面试题:杀掉进程

这个“杀掉进程”的题,说白了就是模拟系统里“结束任务”。 给你三样东西:

  • 一个 pid 数组:所有正在跑的进程 ID
  • 一个 ppid 数组:每个进程的父进程 ID(下标一一对应)
  • 一个 kill:你要干掉的那个进程 ID

要求:把这个进程以及它所有子孙进程的 ID 都找出来,返回成一个列表。

你可以脑补成: 任务管理器里你结束了一个“主程序”,它下面一串子进程也会一起被干掉。

把进程关系想成一棵树

只要看到 “父进程 / 子进程”“要一起删除下面所有的”,其实已经在疯狂暗示:

这玩意儿就是一棵树,从 ppid -> pid 的那种“父指向子”的树。

举个小例子:

pid  = [1, 3, 10, 5]
ppid = [0, 1, 3, 3]
kill = 3

对应关系:

  • 1 的父进程是 0(可以理解为系统)
  • 3 的父进程是 1
  • 10 的父进程是 3
  • 5 的父进程是 3

画成树差不多就是:

0
└── 1
    └── 3
        ├── 10
        └── 5

如果要 kill = 3,那被杀掉的进程就是:[3, 10, 5],自己 + 所有后代。

所以核心问题就变成了:

给你一棵“用数组隐式存”的树,给一个结点,找出它整棵子树的所有结点。

解题思路:先建表,再遍历

思路很自然,两步走:

  1. 把父子关系整理一下用一个字典:parent -> [children...]

  • 遍历 pid 和 ppid
  • 对于每一对 (p, pp),把 p 挂到 children[pp] 下面
  • 从要杀的进程开始,往下把所有子孙都走一遍这个就随便了:

    • 用 DFS(深度优先) 可以
    • 用 BFS(广度优先,队列) 也可以

    走到的每一个结点,都放进答案里,最后返回就行。

    来一段比较接地气的写法,变量名也别太抽象:

    from collections import defaultdict, deque

    defkillProcess(pid, ppid, kill):
    # 1. 建 parent -> children 的映射
        children = defaultdict(list)
    for child, parent in zip(pid, ppid):
            children[parent].append(child)

    # 2. 从 kill 这个点开始 BFS
        res = []
        q = deque([kill])

    while q:
            cur = q.popleft()
            res.append(cur)
    # 把当前结点的所有子进程扔进队列
    for c in children.get(cur, []):
                q.append(c)

    return res

    用刚才那个例子测一下:

    pid  = [1, 3, 10, 5]
    ppid = [0, 1, 3, 3]
    kill = 3

    print(killProcess(pid, ppid, kill))  # 可能输出 [3, 10, 5] 或 [3, 5, 10],顺序无所谓

    5. 换成 DFS 也行(递归版本)

    如果你更喜欢递归,也可以这么写:

    from collections import defaultdict

    defkillProcess(pid, ppid, kill):
        children = defaultdict(list)
    for child, parent in zip(pid, ppid):
            children[parent].append(child)

        res = []

    defdfs(x):
            res.append(x)
    for c in children.get(x, []):
                dfs(c)

        dfs(kill)
    return res

    这个版本的逻辑是:

    • 先把当前进程放进结果
    • 再递归把所有子进程都走一遍

    6. 复杂度

    整体很朴素:

    • 建 children 映射要扫一遍数组,O(n)
    • 遍历树的时候,每个结点最多进一次队列 / 调一次递归,也是 O(n)
    • 所以总时间复杂度:O(n)
    • 额外空间:字典 + 队列 / 递归栈,都是 O(n) 级别

    几个顺手可以注意的小点:

    • 如果 kill 恰好是“根进程”,那结果就是所有进程全被干掉
    • 如果数据量特别大,又用的是递归 DFS,可能会有栈深度问题, 这种时候就更推荐上面那个 队列 BFS 的写法
    • 返回顺序题目一般不要求,只要包含的元素对就行

    这个题本质就是:

    “用 ppid 建一棵树,然后从要删的结点出发,把整棵子树的点都收集起来”。

    思路很直白,实现也不绕弯,面试里属于那种一眼就要想到“树 + 遍历”的题。 你要是能把这个套路记住,碰到类似“删某个结点及所有后代”的题,基本就是同一套写法直接抄过来就能用。

    -END-

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

    🔥虎哥私藏精品🔥

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