Python技术迷

上线前夜,发现致命bug,情急之下直接把服务器上的组件文件给删了,结果整个项目瘫痪,直接收拾东西回了老家,连夜跑路

刚看到个贴子,说有个程序员上线前夜发现致命bug,情急之下直接删了服务器上的组件文件,结果整个项目瘫痪,人也连夜跑路了。

Image

网友们一边笑他“太怂”,一边又感叹“这是真实职场惊魂夜”。

年轻时候没经验,心态又急,遇到问题容易慌神,越想补救越容易越陷越深。删库跑路这四个字,几乎是每个程序员的噩梦,也是成长的必修课。

网友有人说他不负责任,但从另一面看,这也是职场新人最容易暴露的软肋——技术之外的责任感和心理素质。真正成熟的程序员,不是永远不出错,而是出了错能稳住、敢扛、能补。

说到底,删的不是库,是教训。能扛过去,就是经验;扛不过去,就成故事。【备注:文末可领最新资料】

面试题:收集树中金币

我们有一棵 n 个点的树(无向、连通、无环),节点编号 0 ~ n-1。 给你:

  • edges:树的边
  • coins:长度为 n 的数组,coins[i] 要么是 0,要么是 1,表示这个点有没有金币

人一开始在节点 0,每走一条边花费 1 单位时间(来回就是 2),可以在任意节点拐弯、重复走,只要你最后能把所有金币都拿到手,题目问:最少要花多少时间?

你可以理解成:一棵“家谱树”,每个节点有可能藏了一个金币,你从老祖宗 0 出发,把所有金币都收完再回家。

先想想:哪些路是完全没必要走的?

这个题最关键的点,其实是两个字:修剪。

你可以脑补一棵大树:

  • 如果某个叶子(度数=1)下面根本没有金币,那你肯定不会跑去那条支路瞎逛,对吧?
  • 而且如果你把这个没金币的叶子剪掉后,它的父节点又变成“新叶子”,又没金币,那这货也可以继续剪。

所以第一步,我们干一件事:

从所有“没金币的叶子”开始,一层一层往上剪,直到剩下的节点,要么自己有金币,要么是连接金币路径必经之路。

这个过程用一个队列就能搞定:

  • 先把所有 degree == 1 && coins[node] == 0 的点丢进队列
  • 每次弹出一个,把它“删除”(度数-1),连着的点如果也变成了没金币的叶子,再丢进队列
  • 反复直到剪不动

做完这步,你可以想象原来的树被削成了“只保留和金币有关的骨架”。

但就算都跟金币有关,也不一定每条边都要走两次

那接下来还有个优化点。

你想象一下真正去收金币的过程:

  • 某个最偏远的叶子有金币,你一定得走到它再走回来,这条链上的边,咋看都要走两次。
  • 可是越接近中间(接近根)那一段,很多金币的路径是顺路共享的,最后其实有一小段你可以不必再“专门走一趟”。

比较经典的做法是这么玩:

  1. 在“剪完无金币叶子后的树”上,把所有当前的叶子再丢到队列里(这次不管它有没有金币)。

  2. 再剪两层叶子:

  • 每轮把队列里所有点删了,看它们邻居是不是变成新叶子,是就入队。
  • 我们做两轮 BFS,每一轮都删掉当前所有叶子,更新它们邻居的度数。

  • 比如 step = 0,1:

  • 剪完两层之后,剩下的那坨点就是“真正需要来回走的核心区域”; 剩余的边数记成 remainEdges,答案就是 remainEdges * 2。

  • 为啥是“剪两层”?

    你可以这样理解: 从一片金币叶子出发往回走:

    • 最最后那两条边(越靠近叶子那一端)你一定是“专程去一趟又走回来”,没法省;
    • 但更靠上的边,在多个叶子路径之间是可以重用的,不用按“每个叶子单独往返”去算。

    这个算法的推导如果展开会比较长,这里就直接记结论:在保留了所有金币相关节点后,再在整棵树上“去掉两层叶子”,剩下的边数乘 2 就是最短时间。如果最后剩下的边为 0,那就说明根本不用动,或者只有一个点,答案就是 0。

    用 Python 把这个过程写出来

    直接上代码,先看一眼,后面我再快速过一遍:

    from collections import deque
    from typing import List


    classSolution:
    defcollectTheCoins(self, coins: List[int], edges: List[List[int]]) -> int:
            n = len(coins)
    if n <= 1:
    return0

    # 建图 & 度数统计
            g = [[] for _ in range(n)]
            degree = [0] * n
    for u, v in edges:
                g[u].append(v)
                g[v].append(u)
                degree[u] += 1
                degree[v] += 1

    # 当前还存在的边数
            edge_cnt = n - 1

    # 第一次修剪:把“没有金币的叶子”一层层剪掉
            q = deque()
    for i in range(n):
    if degree[i] == 1and coins[i] == 0:
                    q.append(i)

    while q:
                x = q.popleft()
                degree[x] = 0# 相当于删掉
                edge_cnt -= 1if edge_cnt > 0else0# 这个点连着的一定只有一条边

    for y in g[x]:
    if degree[y] > 0:      # 邻居还在树上
                        degree[y] -= 1
    if degree[y] == 1and coins[y] == 0:
                            q.append(y)

    # 如果边都被剪光了,说明根本没有需要走的路
    if edge_cnt <= 0:
    return0

    # 第二次修剪:不管有没有金币,把所有叶子入队
            q.clear()
    for i in range(n):
    if degree[i] == 1:
                    q.append(i)

    # 再剪两层叶子
            steps = 2
    while q and steps > 0:
                size = len(q)
    for _ in range(size):
                    x = q.popleft()
                    degree[x] = 0
                    edge_cnt -= 1

    for y in g[x]:
    if degree[y] > 0:
                            degree[y] -= 1
    if degree[y] == 1:
                                q.append(y)
                steps -= 1

    # 剩下的边需要来回各走一次
    if edge_cnt < 0:
                edge_cnt = 0
    return edge_cnt * 2

    大概过一下流程:

    • g 是邻接表,degree 存每个点当前的度数。

    • edge_cnt = n - 1,树一开始有 n-1 条边,后面每删掉一个节点(叶子)就把对应的边减一。

    • 第一个 while:

      • 从“度数为 1 且没有金币的点”开始往外剪;
      • 每删掉一个,它的父节点有可能变成新的“无金币叶子”,继续剪。
    • 第二阶段:

      • 如果 edge_cnt <= 0,直接 return 0。
      • 否则把当前所有 degree == 1 的点当作叶子,再 BFS 两层(steps = 2),每删一批叶子就更新邻居度数。
    • 最后剩下的 edge_cnt 条边,每条都得来回走一次,所以乘 2。

    时间复杂度是 O(n),因为每条边最多被处理常数次;空间复杂度也是 O(n),主要就是图和队列。

    整套逻辑其实就两句话:

    1. 先把“和金币完全没关系的枝丫”剪掉;
    2. 再从外往里削掉两层叶子,剩下的骨架边数 * 2 就是答案。

    实际写的时候多注意两个细节:

    • 用度数 + 队列来做“多轮修剪”,不要怕改 degree,这个就相当于在树上做拓扑层次删除;
    • edge_cnt 记的是当前还存活的边数,不要忘了边全没了就直接返回 0。

    这样一来,这道“收集树中金币”的题就比较顺了,思路也挺工程化的,跟我们平时做“剪枝优化 + 层次遍历”那一挂是一个套路。

    -END-

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

    🔥虎哥私藏精品🔥

    虎哥作为一名老码农,整理了全网最全《python高级架构师资料合集》,总量高达650GB,点击下方公众号回复关键字 python 全部免费领