#P1463. 第3题-小红走棋
          
                        
                                    
                      
        
              - 
          
          
                      2000ms
            
          
                      Tried: 421
            Accepted: 59
            Difficulty: 7
            
          
          
          
                       所属公司 : 
                              京东
                                
            
                        
              时间 :2023年8月19日
                              
                      
          
 
- 
                        算法标签>动态规划          
 
第3题-小红走棋
思路:动态规划
给定一个棋盘和一个棋子,棋子初始位于(1,1),棋盘由"."、"*"组成,"."可以行走而"*"不行。每次可以将棋子向右、向下、向右下移动若干格,求移动到最右下所需要的步数。
可以发现,走到当前格子所需要的步数和上方格子,左上方格子以及左方格子有关,可以考虑动态规划解决。
定义:dp[i][j][0、1、2]分别表示从上方、从左上方、从左方走到(i,j)所需要的最小的步数
- dp[i][j][0]:从上方dp[i−1][j][0、1、2]转移到当前格,根据状态方程的定义,有$dp[i][j][0] = min(dp[i-1][j][0], dp[i-1][j][1]+1, dp[i-1][j][2]+1)$。即,如果上方格子也是从上方转移来的,那么不用+1,否则另两个方向转移来的需要+1(因为方向变了,如果方向不变,可以将棋子移动任意步)。
 - dp[i][j][1]:从左上方dp[i−1][j−1][0、1、2]转移到当前格,与上面类似,有$dp[i][j][1] = min(dp[i-1][j-1][0]+1, dp[i-1][j-1][1], dp[i-1][j-1][2]+1)$。
 - dp[i][j][2]:从左方dp[i][j−1][0、1、2]转移到当前格,与上面类似,有$dp[i][j][2] = min(dp[i][j-1][0]+1, dp[i][j-1][1]+1, dp[i][j-1][2])$。
 - 如果当前格子为"*",那么dp[i][j][0、1、2]=∞
 
时间复杂度为O(NM)。
代码
C++
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int T;
char s[2005][2005];
int dp[2005][2005][3];
int n, m;
int main() {
    scanf("%d %d", &n, &m); // 输入 n 和 m
    for (int i = 1; i <= n; ++i) {
        scanf("%s", s[i] + 1); // 输入地图的每一行
    }
    memset(dp, 0x7f, sizeof(dp)); // 初始化 dp 数组为最大值
    dp[1][1][0] = 1;
    dp[1][1][1] = 1;
    dp[1][1][2] = 1;
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= m; ++j) {
            if (s[i][j] == '*') {
                dp[i][j][0] = 1e9; // 如果当前位置是障碍物,将对应 dp 值设置为一个大数
                dp[i][j][1] = 1e9;
                dp[i][j][2] = 1e9;
                continue;
            }
            if (i == 1 && j == 1) continue; // 起始位置特殊处理
            dp[i][j][0] = min(dp[i - 1][j][0], min(dp[i - 1][j][1] + 1, dp[i - 1][j][2] + 1));
            dp[i][j][1] = min(dp[i - 1][j - 1][0] + 1, min(dp[i - 1][j - 1][1], dp[i - 1][j - 1][2] + 1));
            dp[i][j][2] = min(dp[i][j - 1][0] + 1, min(dp[i][j - 1][1] + 1, dp[i][j - 1][2]));
        }
    }
    int ans = 1e9; // 初始化答案为一个大数
    ans = min(ans, dp[n][m][0]);
    ans = min(ans, dp[n][m][1]);
    ans = min(ans, dp[n][m][2]);
    if (ans >= 1e9) {
        printf("-1"); // 输出结果
    } else {
        printf("%d", ans);
    }
    return 0;
}
python
n, m = map(int, input().split())  # 输入 n 和 m
s = [['']] * 2005  # 二维列表存储地图
for i in range(1, n + 1):
    s[i] = [''] + list(input())  # 输入地图的每一行
dp = [[[float('inf')] * 3 for _ in range(m + 1)] for _ in range(n + 1)]  # 初始化 dp 数组为最大值
dp[1][1][0] = 1
dp[1][1][1] = 1
dp[1][1][2] = 1
for i in range(1, n + 1):
    for j in range(1, m + 1):
        if s[i][j] == '*':
            dp[i][j][0] = float('inf')  # 如果当前位置是障碍物,将对应 dp 值设置为正无穷
            dp[i][j][1] = float('inf')
            dp[i][j][2] = float('inf')
            continue
        if i == 1 and j == 1:
            continue  # 起始位置特殊处理
        dp[i][j][0] = min(dp[i - 1][j][0], min(dp[i - 1][j][1] + 1, dp[i - 1][j][2] + 1))
        dp[i][j][1] = min(dp[i - 1][j - 1][0] + 1, min(dp[i - 1][j - 1][1], dp[i - 1][j - 1][2] + 1))
        dp[i][j][2] = min(dp[i][j - 1][0] + 1, min(dp[i][j - 1][1] + 1, dp[i][j - 1][2]))
ans = float('inf')  # 初始化答案为正无穷
ans = min(ans, dp[n][m][0])
ans = min(ans, dp[n][m][1])
ans = min(ans, dp[n][m][2])
if ans == float('inf'):
    print("-1")  # 输出结果
else:
    print(int(ans))
Java
import java.util.Scanner;
public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt(); // 输入 n
        int m = scanner.nextInt(); // 输入 m
        
        char[][] s = new char[2005][2005]; // 二维数组存储地图
        for (int i = 1; i <= n; i++) {
            s[i] = (" " + scanner.next()).toCharArray(); // 输入地图的每一行
        }
        
        int[][][] dp = new int[2005][2005][3]; // 初始化 dp 数组
        for (int i = 0; i <= n; i++) {
            for (int j = 0; j <= m; j++) {
                dp[i][j][0] = dp[i][j][1] = dp[i][j][2] = 1_000_000_000; // 初始化为一个大数
            }
        }
        dp[1][1][0] = dp[1][1][1] = dp[1][1][2] = 1;
        
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                if (s[i][j] == '*') {
                    dp[i][j][0] = dp[i][j][1] = dp[i][j][2] = 1_000_000_000; // 如果是障碍物,设为大数
                    continue;
                }
                if (i == 1 && j == 1) continue; // 起始位置特殊处理
                dp[i][j][0] = Math.min(dp[i - 1][j][0], Math.min(dp[i - 1][j][1] + 1, dp[i - 1][j][2] + 1));
                dp[i][j][1] = Math.min(dp[i - 1][j - 1][0] + 1, Math.min(dp[i - 1][j - 1][1], dp[i - 1][j - 1][2] + 1));
                dp[i][j][2] = Math.min(dp[i][j - 1][0] + 1, Math.min(dp[i][j - 1][1] + 1, dp[i][j - 1][2]));
            }
        }
        
        int ans = 1_000_000_000; // 初始化答案为一个大数
        ans = Math.min(ans, dp[n][m][0]);
        ans = Math.min(ans, dp[n][m][1]);
        ans = Math.min(ans, dp[n][m][2]);
        
        if (ans >= 1_000_000_000) {
            System.out.print("-1"); // 输出结果
        } else {
            System.out.print(ans);
        }
    }
}
        题目内容
小红有一个n行m列的棋盘,并且有一个棋子初始位于(1,1)。
棋盘由字符"."、"*"组成,"."代表该位置可行走,"*"代表该位置不能行走。
小红每次可以让棋子往右,往下或者往右下移动若干个位置,他想知道,移动到右下角(n,m)一共需要多少步?
输入描述
第一行,两个正整数n和m。
接下来的n行,每行m个字符,用来表示棋盘
1≤n,m≤2000
输出描述
一个整数,表示所需要的最少步数
样例
输入
4 3
...
.*.
...
**.
输出
2