剑指 Offer 12. 矩阵中的路径
题目
剑指 Offer 12. 矩阵中的路径 难度:medium
给定一个 m x n
二维字符网格 board
和一个字符串单词 word
。如果 word
存在于网格中,返回 true
;否则,返回 false
。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
例如,在下面的 3×4 的矩阵中包含单词 "ABCCED"(单词中的字母已标出)。
示例 1:
输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED"
输出:true
示例 2:
输入: board = [["a","b"],["c","d"]], word = "abcd"
输出: false
提示:
m == board.length
n = board[i].length
1 <= m, n <= 6
1 <= word.length <= 15
board
和word
仅由大小写英文字母组成
方法一:回溯
思路
设函数 $\text{check}(i, j, k)$ 表示判断以网格的 $(i, j)$ 位置出发,能否搜索到单词 $\textit{word}[k..]$,其中 $\textit{word}[k..]$ 表示字符串 $\textit{word}$ 从第 $k$ 个字符开始的后缀子串。如果能搜索到,则返回 $\texttt{true}$,反之返回 $\texttt{false}$。函数 $\text{check}(i, j, k)$ 的执行步骤如下:
- 如果 $\textit{board}[i][j] \neq s[k]$,当前字符不匹配,直接返回 $\texttt{false}$。
- 如果当前已经访问到字符串的末尾,且对应字符依然匹配,此时直接返回 $\texttt{true}$。
- 否则,遍历当前位置的所有相邻位置。如果从某个相邻位置出发,能够搜索到子串 $\textit{word}[k+1..]$,则返回 $\texttt{true}$,否则返回 $\texttt{false}$。
这样,我们对每一个位置 $(i,j)$ 都调用函数 $\text{check}(i, j, 0)$ 进行检查:只要有一处返回 $\texttt{true}$,就说明网格中能够找到相应的单词,否则说明不能找到。
为了防止重复遍历相同的位置,需要额外维护一个与 $\textit{board}$ 等大的 $\textit{visited}$ 数组,用于标识每个位置是否被访问过。每次遍历相邻位置时,需要跳过已经被访问的位置。
解题
Python:
class Solution:
def exist(self, board: List[List[str]], word: str) -> bool:
directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]
def check(i: int, j: int, k: int) -> bool:
if board[i][j] != word[k]:
return False
if k == len(word) - 1:
return True
visited.add((i, j))
result = False
for di, dj in directions:
newi, newj = i + di, j + dj
if 0 <= newi < len(board) and 0 <= newj < len(board[0]):
if (newi, newj) not in visited:
if check(newi, newj, k + 1):
result = True
break
visited.remove((i, j))
return result
h, w = len(board), len(board[0])
visited = set()
for i in range(h):
for j in range(w):
if check(i, j, 0):
return True
return False
Java:
class Solution {
public boolean exist(char[][] board, String word) {
int h = board.length, w = board[0].length;
boolean[][] visited = new boolean[h][w];
for (int i = 0; i < h; i++) {
for (int j = 0; j < w; j++) {
boolean flag = check(board, visited, i, j, word, 0);
if (flag) {
return true;
}
}
}
return false;
}
public boolean check(char[][] board, boolean[][] visited, int i, int j, String s, int k) {
if (board[i][j] != s.charAt(k)) {
return false;
} else if (k == s.length() - 1) {
return true;
}
visited[i][j] = true;
int[][] directions = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
boolean result = false;
for (int[] dir : directions) {
int newi = i + dir[0], newj = j + dir[1];
if (newi >= 0 && newi < board.length && newj >= 0 && newj < board[0].length) {
if (!visited[newi][newj]) {
boolean flag = check(board, visited, newi, newj, s, k + 1);
if (flag) {
result = true;
break;
}
}
}
}
visited[i][j] = false;
return result;
}
}
面试题13. 机器人的运动范围
题目
面试题13. 机器人的运动范围 难度:medium
地上有一个m行n列的方格,从坐标 [0,0]
到坐标 [m-1,n-1]
。一个机器人从坐标 [0, 0]
的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),也不能进入行坐标和列坐标的数位之和大于k的格子。例如,当k为18时,机器人能够进入方格[35, 37],因为3+5+3+7=18。但它不能进入方格 [35, 38],因为3+5+3+8=19。请问该机器人能够到达多少个格子?
示例 1:
输入: m = 2, n = 3, k = 1
输出: 3
示例 2:
输入: m = 3, n = 1, k = 0
输出: 1
提示:
1 <= n,m <= 100
0 <= k <= 20
方法一:递推
思路
定义 vis[i][j]
为 (i, j)
坐标是否可达,如果可达返回 1
,否则返回 0
。
首先 (i, j)
本身需要可以进入,因此需要先判断 i
和 j
的数位之和是否大于 k
,如果大于的话直接设置 vis[i][j]
为不可达即可。
否则,前面提到搜索方向只需朝下或朝右,因此 (i, j)
的格子只会从 (i - 1, j)
或者 (i, j - 1)
两个格子走过来(不考虑边界条件),那么 vis[i][j]
是否可达的状态则可由如下公式计算得到:
$$ vis[i][j]=vis[i−1][j] or vis[i][j−1] $$
即只要有一个格子可达,那么 (i, j)
这个格子就是可达的,因此我们只要遍历所有格子,递推计算出它们是否可达然后用变量 ans
记录可达的格子数量即可。
初始条件 vis[i][j] = 1
,递推计算的过程中注意边界的处理。
解题
Python:
def digitsum(n):
ans = 0
while n:
ans += n % 10
n //= 10
return ans
class Solution:
def movingCount(self, m: int, n: int, k: int) -> int:
vis = set([(0, 0)])
for i in range(m):
for j in range(n):
if ((i - 1, j) in vis or (i, j - 1) in vis) and digitsum(i) + digitsum(j) <= k:
vis.add((i, j))
return len(vis)
Java:
class Solution {
public int movingCount(int m, int n, int k) {
if (k == 0) {
return 1;
}
boolean[][] vis = new boolean[m][n];
int ans = 1;
vis[0][0] = true;
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if ((i == 0 && j == 0) || get(i) + get(j) > k) {
continue;
}
// 边界判断
if (i - 1 >= 0) {
vis[i][j] |= vis[i - 1][j];
}
if (j - 1 >= 0) {
vis[i][j] |= vis[i][j - 1];
}
ans += vis[i][j] ? 1 : 0;
}
}
return ans;
}
private int get(int x) {
int res = 0;
while (x != 0) {
res += x % 10;
x /= 10;
}
return res;
}
}
后记
📝 上篇精讲: 【算法题解】 Day28 双指针
💖 我是 𝓼𝓲𝓭𝓲𝓸𝓽,期待你的关注;
👍 创作不易,请多多支持;
🔥 系列专栏: 算法题解