leetcode576. 出界的路径数
题目链接:https://leetcode-cn.com/problems/out-of-boundary-paths/
记忆化搜索
用dfs函数递归来得到所有解,递归出口为:
- 1.出界了,返回1,路径数+1
- 2.k为0,没有移动次数了,返回0
cache[x][y][k]表示处于坐标(x,y)且还有k次移动次数时能出界的路径数,directions数组包含了四个可移动的方向。
C++代码:
class Solution {
public:
int MOD = 1e9+7;
//因为要在dfs函数里用到m n max,所以需要复制一下
int _m,_n,_max;
int cache[51][51][51];
vector<vector<int>> directions = {{1,0}, {-1,0}, {0,1}, {0,-1}};
int findPaths(int m, int n, int maxMove, int startRow, int startColumn) {
_m = m; _n = n; _max = maxMove;
//全都初始化为-1,表示没有到过这个坐标
//这个memset的用法很妙,memset是以unsigned char的大小初始化的
//而-1刚好是1111 1111,对于int来说也就是4个1111 1111,刚好也表示-1
memset(cache,-1,sizeof(cache));
return dfs(startRow,startColumn,maxMove);
}
int dfs(int x, int y, int k){
//如果越界,证明是一种路径,返回1
if(x<0 || x>=_m || y<0 || y>=_n) return 1;
//如果k的次数用完还未出界,证明无路径可行
if(k == 0) return 0;
//如果重复到达一个坐标,直接返回该坐标出界的路径数
if(cache[x][y][k] != -1) return cache[x][y][k];
int ans = 0;
//遍历一个坐标的四个方向
for(auto& direction : directions){
ans += dfs(x+direction[0], y+direction[1], k-1);
ans %= MOD;
}
//记录该点路径数
cache[x][y][k] = ans;
return ans;
}
};
动态规划
动规五部曲:
-
确定dp数组的定义以及下标的含义
dp[i][j][k]表示球移动i次后到达坐标[j,k]的路径数。 -
递推公式
- 如果移动i+1次没有出界,就把当前位置的坐标数全都
dp[i+1][j'][k'] += dp[i][j][k]; - 如果移动i+1次后出界,
outCounts += dp[i][j][k]
-
dp数组的初始化
i=0时位于起始点,所以dp[0][startRow][startColumn] = 1,其他位置都初始化为0,等待计算 -
遍历顺序
根据移动次数从小到大进行遍历,也就是i从0到maxMove,j和k的顺序无所谓,只要遍历完所有坐标就可以,这里都从0开始 -
举例推导dp数组
除了dp[0][startRow][startColumn]=1,其他所有的dp[0][][]都为0,所以i=0时只会遍历这一个坐标,i=i+1时会遍历起始点周围的四个坐标,以此类推。
class Solution {
public:
static constexpr int MOD = 1'000'000'007;
int findPaths(int m, int n, int maxMove, int startRow, int startColumn) {
vector<vector<int>> directions = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
int outCounts = 0;
vector<vector<vector<int>>> dp(maxMove + 1, vector<vector<int>>(m, vector<int>(n)));
dp[0][startRow][startColumn] = 1;
for (int i = 0; i < maxMove; i++) {
for (int j = 0; j < m; j++) {
for (int k = 0; k < n; k++) {
int count = dp[i][j][k];
if (count > 0) {
for (auto &direction : directions) {
int j1 = j + direction[0], k1 = k + direction[1];
if (j1 >= 0 && j1 < m && k1 >= 0 && k1 < n) {
dp[i + 1][j1][k1] = (dp[i + 1][j1][k1] + count) % MOD;
} else {
outCounts = (outCounts + count) % MOD;
}
}
}
}
}
}
return outCounts;
}
};
时间复杂度和空间复杂度都是O(maxMove × m × n)
可以看到注意到dp[i][][]只在计算dp[i+1][][]时会用到,因此可以将dp中的移动次数的维度省略,将空间复杂度优化到 O(m×n)。
也就是用一个新的dpNew[][]数组来代替dp[i+1][][]。
class Solution {
public:
static constexpr int MOD = 1'000'000'007;
int findPaths(int m, int n, int maxMove, int startRow, int startColumn) {
vector<vector<int>> directions = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
int outCounts = 0;
vector<vector<int>> dp(m, vector<int>(n));
dp[startRow][startColumn] = 1;
for (int i = 0; i < maxMove; i++) {
vector<vector<int>> dpNew(m, vector<int>(n));
for (int j = 0; j < m; j++) {
for (int k = 0; k < n; k++) {
int count = dp[j][k];
if (count > 0) {
for (auto& direction : directions) {
int j1 = j + direction[0], k1 = k + direction[1];
if (j1 >= 0 && j1 < m && k1 >= 0 && k1 < n) {
dpNew[j1][k1] = (dpNew[j1][k1] + count) % MOD;
} else {
outCounts = (outCounts + count) % MOD;
}
}
}
}
}
dp = dpNew;
}
return outCounts;
}
};