Python技术迷

被裁当天得知自己怀孕了,HR知道后,立马将我从裁员名单中去除,本来都要被裁了,现在却留下来,是不是应该感谢宝宝。

这宝宝还没出生,已经开始帮妈妈保住饭碗了?

事情也挺魔幻的。网友本来已经进裁员名单了,估计心里都准备收拾东西走人了,结果当天发现自己怀孕。HR一知道,态度立马变了,裁员名单里直接把她划掉。

Image

你说这事该不该感谢宝宝,当然可以感谢,毕竟这时间点太巧了,跟开了个职场免死金牌似的。但往细了想,也挺让人不是滋味。公司不是突然良心发现,是知道这个阶段裁孕妇风险太高,怕惹麻烦,怕赔钱,怕后面扯不清。

所以宝宝确实是“救场”了,但真正离谱的是,一个人能不能留下来,最后居然靠的是这种偶然。打工人平时加班、绩效、项目,全都不一定稳,反倒是HR听到怀孕俩字,手速比删库还快。

算法题:访问所有节点的最短路径

图里节点不多,但你要是按普通最短路去跑,很快就会绕晕。

这个题最容易写错的地方,不是 BFS,而是“走到同一个节点”这件事根本不够描述当前状态。

比如你现在到了 3 号节点。

一种情况是你已经访问过 0、1、2、3。

另一种情况是你只访问过 3。

这两个状态后面能走出来的答案完全不一样。你要是只用 visited[node] 去判重,基本就废了。我第一眼看这题,先不会想 Dijkstra,边权都是 1,直接 BFS。关键是把“当前在哪个点”和“已经访问了哪些点”绑在一起。

状态长这样:

(node, mask)

node 表示当前停在哪个节点。

mask 用二进制表示访问过哪些节点。

比如一共 4 个点,mask = 0b1011,意思就是访问过 0、1、3,还没访问 2。

目标也很直接:

target = (1 << n) - 1

也就是所有节点对应的二进制位都变成 1。

代码可以这么写:

from collections import deque
from typing import List

classSolution:
defshortestPathLength(self, graph: List[List[int]]) -> int:
        n = len(graph)
if n <= 1:
return0

        full = (1 << n) - 1
        queue = deque()
        seen = set()

for start in range(n):
            state = (start, 1 << start)
            queue.append((start, 1 << start, 0))
            seen.add(state)

while queue:
            cur, mask, step = queue.popleft()

for nxt in graph[cur]:
                next_mask = mask | (1 << nxt)

if next_mask == full:
return step + 1

                next_state = (nxt, next_mask)
if next_state in seen:
continue

                seen.add(next_state)
                queue.append((nxt, next_mask, step + 1))

return0

这里有个小细节,BFS 不是从某一个节点开始,而是所有节点一起入队。

为什么?

因为题目没规定起点。你可以从任意节点出发,那就别傻乎乎枚举每个起点跑一遍 BFS。把每个节点都当成初始状态塞进队列,本质上就是多源 BFS,谁先走到全访问状态,谁就是最短路径。

这题还有一个容易误判的点:节点可以重复访问。

很多人看到“访问所有节点”,下意识以为每个节点只能走一次。不是。这个题求的是最短路径长度,不是哈密顿路径。图可能长得很别扭,中间节点来回踩几次很正常。

比如:

graph = [[1], [0, 2, 4], [1, 3], [2], [1]]

从左边走到右边,再回来拐到另一个分支,中间的 1 肯定会重复经过。这个重复不是问题,问题是同一个 (node, mask) 没必要重复进队。

我一般判断这题能不能用状态压缩,就看两个东西。

第一,节点数量小。

第二,某个状态能用二进制很干净地表示。

这题刚好都满足。n 个节点,mask 最多 2^n 种,每种状态还要乘上当前节点,所以状态数量大概是:

n * 2^n

BFS 每次再扫一遍相邻边,复杂度能接受。

这段代码里真正值钱的不是那几行位运算,而是判重方式:

seen.add((nxt, next_mask))

不是只看 nxt。

只看当前节点,信息丢了。

只看访问集合,也不够,因为你停在不同节点,下一步能走的边也不一样。

两个条件合在一起,状态才完整。这个点想明白了,这题就没什么花活了。