Python技术迷

有位同事被解雇了。他立刻退出了工作群,删除了同事和领导的联系。到了中午,组长试图联系他,但发现自己也被删除了~

早上人还在工位上敲键盘,中午已经把整个部门通讯录清空了,这画面确实有点猛。

同事被裁,消息一下来,群退了,好友删了,领导也一起打包拉黑。等到中午组长想找人,才发现自己早没了。再叫别人联系,结果一个都联系不上,属于是物理离职还没走,社交离职先一步完成。

Image

评论区有人说这哥们太绝情,也有人说这才是标准动作,都要走了还留着干嘛,等着以后被人“顺手问点事”吗。我看这事真不算离谱。公司通知裁人那一刻,关系基本就从同事切换成前同事了。你跟公司讲体面,公司未必跟你讲温度。删得这么快,多少带点情绪,但也挺真实。成年人翻脸,不一定吵,点几下删除就够了。

算法题:破解保险箱

四个轮子,每个轮子 10 个数字,起点还是 "0000"。这题看着像暴力,真上手乱拧两圈就知道不对。

你如果每次都想着“离目标近一点”,大概率会掉坑里。因为保险箱这玩意儿不是爬楼梯,它更像图搜索:每拨一次某一位的上一格或下一格,都是一次合法状态转移。题里再塞几个 deadends,很多人代码还能跑,结果一超时就露馅了。

这题我第一眼就不太信 DFS。原因很简单,题目要的是最少次数,不是问你能不能到。只要看到“最短步数 + 状态转换均匀”,基本就该往 BFS 上靠了。这个判断比背模板重要。

先把状态想明白。 比如 "0000",拨第一位往下是 "9000",往上是 "1000"。注意这里有环,0 的前一位不是 -1,是 9。这个地方手一抖就容易写错。

我一般会先写个生成下一层状态的小函数,别一股脑全塞进主流程里,不然后面 debug 很烦。

from collections import deque

defnext_states(state: str):
for i, ch in enumerate(state):
        x = int(ch)

        up = state[:i] + str((x + 1) % 10) + state[i + 1:]
        down = state[:i] + str((x - 1) % 10) + state[i + 1:]

yield up
yield down

主逻辑就清楚了: 队列里放当前能到达的状态;visited 防止来回拨; 每扩一层,步数加一。

defopen_lock(deadends, target: str) -> int:
    dead = set(deadends)

if"0000"in dead:
return-1
if target == "0000":
return0

    q = deque([("0000", 0)])
    visited = {"0000"}

while q:
        state, step = q.popleft()

for nxt in next_states(state):
if nxt in dead or nxt in visited:
continue
if nxt == target:
return step + 1

            visited.add(nxt)
            q.append((nxt, step + 1))

return-1

拿经典例子试一下:

deadends = ["0201", "0101", "0102", "1212", "2002"]
target = "0202"

print(open_lock(deadends, target))   # 6

这题真正的坑,不在 BFS 本身,在几个小地方:

第一,visited 别等出队再加,入队就加。 不然同一个状态可能被重复塞进队列,量一大就炸。

第二,deadends 一定先转 set。 你要是还拿 list 去查,代码表面没错,性能已经开始掉了。

第三,不要写成“朝 target 靠近”的贪心。 比如目标某一位是 9,你以为从 0 往下拨一步就行,但别的位可能被死锁挡住。局部最优在这题里经常没用。

这题说白了,就是把保险箱当成一张图: 每个 4 位数字是一个节点,每次拨动产生 8 条边。 BFS 一层层推过去,第一次遇到 target,就是最短路径。