目录
题目描述
思路解析1
按照题意我们需要走遍所有的可行格子才可以。所以,我们只需要判断从 “1” 到 “2” 的路径中是否走完了所有可行路径的格子就可以了。这也是最简单直观的代码,也是最基本、基础的代码。
AC代码1
class Solution {
public:
int uniquePathsIII(vector<vector<int>>& grid) {
int r = grid.size(), c = grid[0].size();
int si = 0, sj = 0, n = 0;
//预处理,统计可行格子数量、起始点
for (int i = 0; i < r; i++) {
for (int j = 0; j < c; j++) {
if (grid[i][j] == 0) {
n++;
} else if (grid[i][j] == 1) {
n++;
si = i;
sj = j;
}
}
}
//编写函数类 dfs,从(i, j)出发剩余n个可行格子
function<int(int, int, int)> dfs = [&](int i, int j, int n) -> int {
if (grid[i][j] == 2) {
if (n == 0) {//如果可行格子都走完,合题路径+1
return 1;
}
return 0;//否则,+0
}
int t = grid[i][j], res = 0;
grid[i][j] = -1;
vector<array<int, 2>> dir({
{-1, 0}, {1, 0}, {0, -1}, {0, 1}});
for (auto &[di, dj] : dir) {//四个方向尝试
int ni = i + di;
int nj = j + dj;
if (ni >= 0 && ni < r && nj >= 0 && nj < c && \
(grid[ni][nj] == 0 || grid[ni][nj] == 2)) {
res += dfs(ni, nj, n - 1);
}
}
grid[i][j] = t;//还原
return res;
};
return dfs(si, sj, n);
}
};
复杂度分析
因为每一个格子都需要向四个方向尝试,同时总格子数量为 rows * column ,所以最终的代码复杂度在 。你想得没错,这道题leetcode的数据不是很强...在 0 <= rows * columns <= 20 的情况下,这个复杂度是不能通过所有案例的。可是leetcode官方数据比较弱,就导致没有卡该算法。所以复制粘贴谨慎,保不齐日后数据更新被卡。
思路解析2
思路之所以诞生可以认为是算法1可能被卡,所以去绞尽脑汁去优化的代码。
面对DFS的优化,我们往往有剪枝、记忆化、状态压缩三种简单的方式。
剪枝显然不太行,因为我们不能断言进入某个方向后是不符合题意的。所以目光来到记忆化。
记忆化顾名思义就是通过记忆某种状态,当下次再次计算到一样的情形的时候就可以直接调用。
那么如何确定一个一致的情况,我们需要一下几个元素确定 (当前点,未访问的可行点)
当这两个值一致时,我们就可以直接访问先前计算的答案。如果你不理解,不妨画一个图来确定完全一致的情形。
正是两个二元组,可以视为一个小集合,面对集合我们可以使用状态压缩成key,同理可行点的状态可以状态压缩成st。
AC代码2
class Solution {
public:
int uniquePathsIII(vector<vector<int>>& grid) {
int r = grid.size(), c = grid[0].size();
int si = 0, sj = 0, st = 0;
unordered_map<int, int> memo;//记忆数组
//预处理,可行点--注意目的地也是可行点、起点
for (int i = 0; i < r; i++) {
for (int j = 0; j < c; j++) {
if (grid[i][j] == 0 || grid[i][j] == 2) {
st |= (1 << (i * c + j));
} else if (grid[i][j] == 1) {
si = i, sj = j;
}
}
}
//在(i, j) 情形要素可行点st的路径数
function<int(int ,int, int)> dp = [&](int i, int j, int st) -> int {
if (grid[i][j] == 2) {
if (st == 0) {
return 1;
}
return 0;
}
//设置访问情形
int key = ((i * c + j) << (r * c)) + st;
if (!memo.count(key)) {
int res = 0;
vector<array<int, 2>> dir({
{-1, 0}, {1, 0}, {0, -1}, {0, 1}});
for (auto &[di, dj] : dir) {
int ni = i + di, nj = j + dj;
if (ni >= 0 && ni < r && nj >= 0 && nj < c && (st & (1 << (ni * c + nj))) > 0) {//该点合法、未走过
res += dp(ni, nj, st ^ (1 << (ni * c + nj)));//标记访问过
}
}
memo[key] = res;//记录 {(i,j), st} 情形的合题路数
}
return memo[key];
};
return dp(si, sj, st);
}
};
复杂度分析
此时每个点只有访问与未被访问两种状态,所以状态数为
因此,时间复杂度也为 O()。