Advent of Code 2025 挑战全手写代码 Day 11 - 反应堆

52 阅读3分钟

🎄 Advent of Code 2025 挑战全手写代码 Day 11 - 反应堆


大家好,昨天(day 10)是不是也觉得很有挑战?今天 day 11 轻松一点了!今日题目 Reactor 难度中等(三星 ⭐⭐⭐),主要考察有向无环图(DAG)路径计数递归深度优先搜索(DFS) 等知识点。

advent-of-code-2025-day-11

📖 题目速览

  • 题目地址:adventofcode.com/2025/day/11
  • 背景:我们来到了反应堆维护室,需要帮助小精灵修复设备连接。
  • 输入格式:一系列设备及其连接关系,例如 aaa: bbb ccc ,表示设备 aaa 指向 bbb 和 ccc 。这构成了一个有向图。题目中提到数据只能顺流而下("can't flow backwards"),暗示这是一个有向无环图(DAG)。

第一部分:计算从起点 "you" 到终点 "out" 的所有不同路径的数量。 第二部分:计算从起点 "svr" 到终点 "out" 的所有路径中,既经过节点 "dac" 又经过节点 "fft" (谁先谁后都可以)的路径数量。

🧠 解题思路 (Python 🐍)

Part 1:

  • 从起点 "you" 开始,使用深度优先搜索(DFS)遍历所有可能的路径。
  • 每次遍历到一个节点时,检查是否到达终点 "out"。如果是,记录一条路径。
  • 最后返回记录的路径数量。

递归部分代码如下:

def _count_paths(self, source: str) -> int:
    if "out" in self.connections[source]:
        return 1

    out: int = 0

    for key in self.connections[source]:
        out += self._count_paths(source=key)

    return out

使用

self._count_paths(source="you")

即可求得从 "you""out" 的所有不同路径的数量。

Part 2:

  • 若遍历所有 svr 到 out 的路径,再记录经过 dac 和 fft 的路径数,既效率低下,也没有必要。
  • 由于必须经过两个特定中间点,且顺序不限,路径只可能是以下两种情况之一:
  1. svr -> ... -> dac -> ... -> fft -> ... -> out
  2. svr -> ... -> fft -> ... -> dac -> ... -> out
  • 我们可以复用第一部分的路径计数逻辑,编写一个通用的 count_paths(start, end) 函数。
  • 情况 1 的路径数 = count_paths('svr', 'dac') * count_paths('dac', 'fft') * count_paths('fft', 'out')
  • 情况 2 的路径数 = count_paths('svr', 'fft') * count_paths('fft', 'dac') * count_paths('dac', 'out')
  • 最终结果为这两种情况之和。

可将 part 1 中的递归函数改造为:

def _count_paths(self, source: str, target: str) -> int:
    if source == "out":
        return 0

    if target in self.connections[source]:
        return 1

    out: int = 0

    for key in self.connections[source]:
        out += self._count_paths(source=key, target=target)

    return out

使用

self._count_paths(source="svr", target="dac")

即可求得从 "svr""dac" 的所有不同路径的数量。 以此类推,即可求得其它节点之间的路径数量。

然而这种解法虽然在示例数据上能够通过,但在实际数据上会超时。 这是因为在递归过程中,会重复计算一些路径。 例如,从 "svr""dac" 的路径数,会被计算多次。 为了避免重复计算,我们可以使用 Memoization 技术,即缓存已经计算过的路径数。

一种思路是使用 Python 标准库中的 functools.cache 装饰器(LRU 缓存),记忆已经计算过的路径数。一旦计算过某个节点的路径数,下次直接返回结果,将时间复杂度从指数级降低到线性 O(V+E)O(V+E)

import functools

def count_paths(self, start: str, end: str) -> int:
    """
    Count all paths from start to end using DFS with memoization.
    """
    @functools.cache
    def dfs(current: str) -> int:
        if current == end:
            return 1
        if current not in self.connections:
            return 0

        total_paths = 0
        for neighbor in self.connections[current]:
            total_paths += dfs(neighbor)
        return total_paths

    return dfs(start)

结语

今天题目的核心在于实现一个高效的“两点间路径计数”函数。只要图是无环的,通过简单的 DFS + Memoization 即可完美解决。 今天题目难度下降,应该不会花费太多力气就可以拿到两颗星星⭐⭐ 明天(Day 12)是最后一天了,坚持就是胜利!记得继续冲榜!🚀

day-11-ranking