题目链接

小红的皇后

题目描述

在一个 的棋盘上,有一些格子是障碍物(*),其余为空格(.)。一个皇后从左上角 出发,目标是到达右下角 。皇后每次移动可以沿右、下、右下三个方向之一前进任意多格,且移动路径上不能有障碍物。求从起点到终点的最少移动步数。

解题思路

本题可以看作一个最短路径问题,但由于边的权重特殊(每次移动,无论多远,成本都是1),使用动态规划是解决此类问题的经典方法。

核心思想

问题的核心在于,沿同一个方向连续移动不增加步数,但转换方向则需要增加一步。这启发我们,DP的状态不仅要记录位置,还必须记录是“通过哪种方向的移动”到达该位置的。

动态规划设计

  1. 状态定义: 表示到达格子 所需的最少移动步数,其中 代表最后一步移动的方向

    • : 最后一步是向下移动。
    • : 最后一步是向右移动。
    • : 最后一步是向右下移动。
  2. 状态转移: 对于一个非障碍格子 ,我们分别计算 , , 的值。

    • 计算 (向下到达): 我们必然是从 移动过来的。

      • 情况一:延续向下的移动。如果到达 的最后一步也是向下的,我们可以直接延伸这条路径,步数不变。成本为
      • 情况二:从 开始一次新的向下移动。如果到达 的最后一步是向右或向右下,那么从 就必须开始一次新的移动,步数加 1。成本为
      • 综合起来:
    • 计算 (向右到达): 同理,我们从 移动过来。

    • 计算 (向右下到达): 同理,我们从 移动过来。

  3. 初始化:

    • 将整个 数组初始化为一个极大值(代表不可达)。
    • 对于起点 ,我们定义到达它需要 1 步(即第一步)。所以,如果 grid[0][0] 不是障碍物,我们设置 。这表示从起点可以开始任意方向的移动,成本都是 1。
    • 如果 ,起点即终点,步数为 0,需要特判。
  4. 最终答案: 遍历完所有格子后,终点 的最少步数就是三种方式到达它的步数中的最小值:。如果这个值仍然是极大值,则说明终点不可达。

代码

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>

using namespace std;

const int INF = 1e9;

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);

    int n, m;
    cin >> n >> m;
    vector<string> grid(n);
    for (int i = 0; i < n; ++i) {
        cin >> grid[i];
    }

    if (n == 1 && m == 1) {
        cout << 0 << endl;
        return 0;
    }

    vector<vector<vector<int>>> dp(n, vector<vector<int>>(m, vector<int>(3, INF)));

    if (grid[0][0] == '.') {
        dp[0][0][0] = 1;
        dp[0][0][1] = 1;
        dp[0][0][2] = 1;
    }

    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < m; ++j) {
            if (grid[i][j] == '*') {
                continue;
            }

            if (i > 0 && grid[i-1][j] == '.') {
                int cost = min(dp[i-1][j][0], min(dp[i-1][j][1], dp[i-1][j][2]) + 1);
                dp[i][j][0] = min(dp[i][j][0], cost);
            }
            if (j > 0 && grid[i][j-1] == '.') {
                int cost = min(dp[i][j-1][1], min(dp[i][j-1][0], dp[i][j-1][2]) + 1);
                dp[i][j][1] = min(dp[i][j][1], cost);
            }
            if (i > 0 && j > 0 && grid[i-1][j-1] == '.') {
                int cost = min(dp[i-1][j-1][2], min(dp[i-1][j-1][0], dp[i-1][j-1][1]) + 1);
                dp[i][j][2] = min(dp[i][j][2], cost);
            }
        }
    }

    int ans = min({dp[n-1][m-1][0], dp[n-1][m-1][1], dp[n-1][m-1][2]});

    if (ans >= INF) {
        cout << -1 << endl;
    } else {
        cout << ans << endl;
    }

    return 0;
}
import java.util.Scanner;
import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int m = sc.nextInt();
        char[][] grid = new char[n][m];
        for (int i = 0; i < n; i++) {
            grid[i] = sc.next().toCharArray();
        }

        if (n == 1 && m == 1) {
            System.out.println(0);
            return;
        }

        final int INF = 1_000_000_000;
        int[][][] dp = new int[n][m][3];
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                Arrays.fill(dp[i][j], INF);
            }
        }

        if (grid[0][0] == '.') {
            dp[0][0][0] = 1;
            dp[0][0][1] = 1;
            dp[0][0][2] = 1;
        }

        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                if (grid[i][j] == '*') {
                    continue;
                }

                if (i > 0 && grid[i - 1][j] == '.') {
                    int cost = Math.min(dp[i-1][j][0], Math.min(dp[i-1][j][1], dp[i-1][j][2]) + 1);
                    dp[i][j][0] = Math.min(dp[i][j][0], cost);
                }
                if (j > 0 && grid[i][j - 1] == '.') {
                    int cost = Math.min(dp[i][j-1][1], Math.min(dp[i][j-1][0], dp[i][j-1][2]) + 1);
                    dp[i][j][1] = Math.min(dp[i][j][1], cost);
                }
                if (i > 0 && j > 0 && grid[i - 1][j - 1] == '.') {
                    int cost = Math.min(dp[i-1][j-1][2], Math.min(dp[i-1][j-1][0], dp[i-1][j-1][1]) + 1);
                    dp[i][j][2] = Math.min(dp[i][j][2], cost);
                }
            }
        }

        int ans = Math.min(dp[n - 1][m - 1][0], Math.min(dp[n - 1][m - 1][1], dp[n - 1][m - 1][2]));

        if (ans >= INF) {
            System.out.println(-1);
        } else {
            System.out.println(ans);
        }
    }
}
import sys

def solve():
    n, m = map(int, sys.stdin.readline().split())
    grid = [sys.stdin.readline().strip() for _ in range(n)]

    if n == 1 and m == 1:
        print(0)
        return

    INF = float('inf')
    # dp[i][j][k] k=0:Down, k=1:Right, k=2:Right-Down
    dp = [[[INF] * 3 for _ in range(m)] for _ in range(n)]

    if grid[0][0] == '.':
        dp[0][0][0] = 1
        dp[0][0][1] = 1
        dp[0][0][2] = 1

    for i in range(n):
        for j in range(m):
            if grid[i][j] == '*':
                continue

            # Arriving from Above (Down move)
            if i > 0 and grid[i - 1][j] == '.':
                cost = min(dp[i-1][j][0], min(dp[i-1][j][1], dp[i-1][j][2]) + 1)
                dp[i][j][0] = min(dp[i][j][0], cost)
            
            # Arriving from Left (Right move)
            if j > 0 and grid[i][j - 1] == '.':
                cost = min(dp[i][j-1][1], min(dp[i][j-1][0], dp[i][j-1][2]) + 1)
                dp[i][j][1] = min(dp[i][j][1], cost)

            # Arriving from Top-Left (Right-Down move)
            if i > 0 and j > 0 and grid[i - 1][j - 1] == '.':
                cost = min(dp[i-1][j-1][2], min(dp[i-1][j-1][0], dp[i-1][j-1][1]) + 1)
                dp[i][j][2] = min(dp[i][j][2], cost)

    ans = min(dp[n-1][m-1])
    
    if ans == INF:
        print(-1)
    else:
        print(ans)

solve()

算法及复杂度

  • 算法:动态规划 (DP)

  • 时间复杂度:

    • 我们需要遍历整个 的棋盘。在每个格子,我们执行常数次的状态转移计算。因此,总时间复杂度为
  • 空间复杂度:

    • 主要的空间开销来自于存储状态的 数组,其大小为
    • 优化提示:由于 的计算只依赖于 , ,可以使用滚动数组或只保留前一行和当前行的状态,将空间复杂度优化到