01BFS算法(例题:网格传送门旅游)

✅博客主页:爆打维c-CSDN博客​​​​​​ 🐾

一、分析题目

原题力扣链接:https://leetcode.cn/problems/grid-teleportation-traversal/description/

这是一道典型的网格图上找最短路径的问题

  1. 传送门权重为0(题目说明"通过传送门不计入移动次数")
  2. 普通单元格权重为1

当图中的边权只有 0 和 1 时,最适合的算法是 0-1 BFS(双端队列广度优先搜索)。使用 Dijkstra 也可以,但 0-1 BFS 的时间复杂度更优,为 O(M×N)。

解题思路
1.预处理传送门位置:

遍历整个网格,使用一个哈希表(或数组)记录每种字符('A'-'Z')出现的所有坐标位置。

groups['A'] = [(r1, c1), (r2, c2), ...]
2.0-1 BFS 状态定义:

队列中存储元组 (row, col, cost)。row, col: 当前坐标;cost: 到达该点的最少移动次数。

3.核心逻辑:

使用双端队列 deque:

当我们从当前点 (r, c)走向相邻格子(上下左右)时,代价+1,将新状态加入队列尾部。

当我们从当前点 (r, c)使用传送门传送到同字母的其他格子时,代价+0,将新状态加入队列 头部(因为花费没有增加,应该优先处理)。

4.防止死循环与优化:

普通去重:

  • dist 数组:既充当了记录最短距离的作用,也充当了 visited 的作用。如果我们发现到达某个点的 new_cost 大于等于已记录的 dist[r][c],就不需要再次入队。

传送门去重:

  • IsUsed 数组:题目规定"每个字母对应的传送门在旅程中最多只能使用一次"。这不仅是规则,也是优化的关键。当我们第一次到达某个字符(比如 'A')并触发了传送,我们将所有其他的 'A' 都加入了队列。如果我们再次遇到 'A',没有必要再传送一次(因为之前的路径肯定更短或相等)。因此,我们需要一个数组来记录已经触发过的字母。

二、算法原理

01BFS(双端队列广度优先搜索): 维护一个双端队列 (std::deque)。

  • 如果边的权重是 0 (传送),我们将新节点加入队列的 头部 (Front)
    • 这意味着我们不需要花费额外代价就能到达那里,应该立即处理它。
  • 如果边的权重是 1 (移动),我们将新节点加入队列的 尾部 (Back)
    • 这保证了队列中的节点始终按照"当前最小代价"排序。

通常我们用 BFS 处理边权全为 1 的图,用 Dijkstra 处理边权非负的图。但在边权只有 0 和 1 的情况下,Dijkstra 的O(ElogV) 有点慢,而 0-1 BFS 可以在 O(V+E) 的线性时间内解决问题。

01BFS例题出入队列过程

三、实现代码

C++版

cpp 复制代码
#include <vector>
#include <string>
#include <deque>
#include <tuple>
#include <climits> // for INT_MAX

using namespace std;

class Solution {
public:
    int minMoves(vector<string>& matrix) {
        int m = matrix.size();
        int n = matrix[0].size();

        // 1. 预处理:记录每种字母出现的所有位置
        // group[0] 存储 'A' 的坐标,group[1] 存储 'B' 的坐标...
        vector<pair<int, int>> group[26];
        for (int i = 0; i < m; ++i) {
            for (int j = 0; j < n; ++j) {
                if (isupper(matrix[i][j])) {
                    group[matrix[i][j] - 'A'].push_back({i, j});
                }
            }
        }

        // 2. 初始化数据结构
        // dist[i][j] 记录到达 (i, j) 的最小步数,初始化为无穷大
        vector<vector<int>> dist(m, vector<int>(n, INT_MAX));
        // 双端队列,存储坐标 {row, col}
        deque<pair<int, int>> dq;

        // 起点初始化
        dist[0][0] = 0;
        dq.push_front({0, 0});

        // 记录某个字母的传送门是否已经被使用(展开)过,避免重复计算
        vector<bool> portalUsed(26, false);

        // 方向数组:上、下、左、右
        int dx[4] = {-1, 1, 0, 0};
        int dy[4] = {0, 0, -1, 1};

        while (!dq.empty()) {
            auto [r, c] = dq.front();
            dq.pop_front();

            // 当前花费
            int current_cost = dist[r][c];

            // 到达终点
            if (r == m - 1 && c == n - 1) {
                return current_cost;
            }

            char cell = matrix[r][c];

            // --- 逻辑 A: 尝试使用传送门 (权重为 0) ---
            if (isupper(cell)) {
                int pIdx = cell - 'A';
                // 如果这个字母的传送门还没被使用过
                if (!portalUsed[pIdx]) {
                    portalUsed[pIdx] = true; // 标记已使用,防止后续重复入队
                    for (auto& next_pos : group[pIdx]) {
                        int nr = next_pos.first;
                        int nc = next_pos.second;
                        
                        // 松弛操作:如果找到了更短的路径
                        if (dist[nr][nc] > current_cost) {
                            dist[nr][nc] = current_cost; // 传送不增加步数
                            dq.push_front({nr, nc});     // 权重为0,加到队首
                        }
                    }
                }
            }

            // --- 逻辑 B: 尝试普通移动 (权重为 1) ---
            for (int i = 0; i < 4; ++i) {
                int nr = r + dx[i];
                int nc = c + dy[i];

                // 检查边界和障碍物
                if (nr >= 0 && nr < m && nc >= 0 && nc < n && matrix[nr][nc] != '#') {
                    // 松弛操作
                    if (dist[nr][nc] > current_cost + 1) {
                        dist[nr][nc] = current_cost + 1; // 移动增加1步
                        dq.push_back({nr, nc});          // 权重为1,加到队尾
                    }
                }
            }
        }

        return -1; // 无法到达
    }
};

Python版

python 复制代码
from collections import deque, defaultdict

class Solution:
    def minMoves(self, matrix: list[str]) -> int:
        m, n = len(matrix), len(matrix[0])
        
        # 1. 预处理:记录所有传送门的位置
        portals = defaultdict(list)
        for r in range(m):
            for c in range(n):
                char = matrix[r][c]
                if 'A' <= char <= 'Z':
                    portals[char].append((r, c))
        
        # 2. 初始化 0-1 BFS
        # 队列存储: (r, c, cost)
        q = deque([(0, 0, 0)])
        
        # 记录已访问的坐标,避免重复处理
        visited = [[False] * n for _ in range(m)]
        visited[0][0] = True
        
        # 记录已使用过的传送门字母,避免重复展开
        used_portal_chars = set()
        
        # 方向数组
        directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]
        
        while q:
            r, c, cost = q.popleft()
            
            # 到达终点
            if r == m - 1 and c == n - 1:
                return cost
            
            curr_char = matrix[r][c]
            
            # --- 逻辑 A: 尝试使用传送门 (权重为 0) ---
            # 如果当前是传送门字母,且该字母未被使用过
            if 'A' <= curr_char <= 'Z' and curr_char not in used_portal_chars:
                used_portal_chars.add(curr_char) # 标记该字母已使用
                
                for nr, nc in portals[curr_char]:
                    if not visited[nr][nc]:
                        visited[nr][nc] = True
                        # 代价为 0,加入队首
                        q.appendleft((nr, nc, cost))
            
            # --- 逻辑 B: 尝试普通移动 (权重为 1) ---
            for dr, dc in directions:
                nr, nc = r + dr, c + dc
                
                # 检查边界和障碍物
                if 0 <= nr < m and 0 <= nc < n and matrix[nr][nc] != '#':
                    if not visited[nr][nc]:
                        visited[nr][nc] = True
                        # 代价为 1,加入队尾
                        q.append((nr, nc, cost + 1))
                        
        # 无法到达
        return -1
相关推荐
喵手2 小时前
Python爬虫零基础入门【第六章:增量、去重、断点续爬·第3节】幂等去重:同一条数据反复跑也不会重复入库!
爬虫·python·爬虫实战·python爬虫工程化实战·零基础python爬虫教学·增量、去重、断点续爬·幂等去重
Python毕设指南2 小时前
基于深度学习的旅游推荐系统
python·深度学习·数据分析·django·毕业设计·课程设计
像素猎人2 小时前
力扣:面试题16.01.交换数字
c++·算法·leetcode·面试
小O的算法实验室2 小时前
2024年ASOC SCI2区TOP,异构 pbest 引导的综合学习粒子群算法,深度解析+性能实测
算法·论文复现·智能算法·智能算法改进
深蓝电商API2 小时前
Selenium多窗口切换与Cookie管理
爬虫·python·selenium·测试工具
AI科技星2 小时前
从质能关系到时空几何:光速飞行理论的框架对比与逻辑验证
服务器·人工智能·线性代数·算法·矩阵
CSDN_RTKLIB2 小时前
C++仿函数
c++·算法·stl
小北方城市网2 小时前
Spring Cloud 服务治理实战:构建高可用微服务体系
spring boot·python·rabbitmq·java-rabbitmq·数据库架构
学嵌入式的小杨同学2 小时前
【嵌入式 C 语言高频考点】周测 + 期中真题解析:从基础语法到编程实战
c语言·数据结构·数据库·vscode·算法·面试