剑指offer-65、矩阵中的路径
<h2 id="题目描述">题目描述</h2><p>请设计⼀个函数,⽤来判断在⼀个矩阵中是否存在⼀条包含某字符串所有字符的路径。路径可以从矩阵中的任意⼀个格⼦开始,每⼀步可以在矩阵中向左,向右,向上,向下移动⼀个格⼦。如果⼀条路径经过了矩阵中的某⼀个格⼦,则该路径不能再进⼊该格⼦。 例如矩阵:</p>
<p><img src="https://seven97-blog.oss-cn-hangzhou.aliyuncs.com/imgs/202503161739591.png" alt="" loading="lazy"></p>
<p>中包含⼀条字符串 " bcced " 的路径,但是矩阵中不包含 " abcb " 路径,因为字符串的第⼀个字符 b占据了矩阵中的第⼀⾏第⼆个格⼦之后,路径不能再次进⼊该格⼦。</p>
<p>示例1</p>
<p>输⼊:<code>[,,],"abcced"</code><br>
返回值:<code>true</code></p>
<h2 id="思路及解答">思路及解答</h2>
<h3 id="dfs回溯">DFS回溯</h3>
<p>主要的思路是对于每⼀个字符为起点,递归向四周拓展,然后遇到不匹配返回 false ,匹配则接着匹配直到完成,⾥⾯包含了 回溯 的思想。步骤如下:</p>
<p>针对每⼀个字符为起点,初始化⼀个和矩阵⼀样⼤⼩的标识数组,标识该位置是否被访问过,⼀开始默认是false 。</p>
<ol>
<li>如果当前的字符索引已经超过了字符串⻓度,说明前⾯已经完全匹配成功,直接返回 true</li>
<li>如果⾏索引和列索引,不在有效的范围内,或者改位置已经标识被访问,直接返回 false</li>
<li>否则将当前标识置为已经访问过</li>
<li>如果矩阵当前位置的字符和字符串相等,那么就字符串的索引加⼀,递归判断周边的四个,只要⼀个的结果为 true ,就返回 true ,否则将该位置置为没有访问过(相当于回溯,退回上⼀步),返回 false 。矩阵当前位置的字符和字符串不相等,否则同样也是将该位置置为没有访问过(相当于回溯,退回上⼀步),返回 false 。</li>
</ol>
<p>⽐如查找 bcced :</p>
<p><img src="https://seven97-blog.oss-cn-hangzhou.aliyuncs.com/imgs/202503161741890.png" alt="" loading="lazy"></p>
<pre><code class="language-java">public class Solution {
public boolean hasPath(char[][] matrix, String word) {
// write code here
if (matrix == null || word == null || word.length() == 0) {
return false;
}
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix.length; j++) {
boolean[][] flags = new boolean.length];
boolean result = judge(i, j, matrix, flags, word, 0);
if (result) {
return true;
}
}
}
return false;
}
public boolean judge(int i, int j, char[][] matrix, boolean[][] flags, String words, int index) {
if (index >= words.length()) {
return true;
}
if (i < 0 || j < 0 || i >= matrix.length || j >= matrix.length || flags) {
return false;
}
flags = true;
if (matrix == words.charAt(index)) {
if (judge(i - 1, j, matrix, flags, words, index + 1)
|| judge(i + 1, j, matrix, flags, words, index + 1)
|| judge(i, j + 1, matrix, flags, words, index + 1)
|| judge(i, j - 1, matrix, flags, words, index + 1)) {
return true;
} else {
flags = false;
return false;
}
} else {
flags = false;
return false;
}
}
}
</code></pre>
<ul>
<li><strong>时间复杂度</strong>:O(3^k × m × n),其中k为单词长度,m、n为矩阵尺寸。每个点有3个方向可选(不能回退)</li>
<li><strong>空间复杂度</strong>:O(k),递归栈深度和visited数组空间</li>
</ul>
<h3 id="方向数组优化">方向数组优化</h3>
<p>使用额外的访问标记数组来记录路径状态。</p>
<pre><code class="language-java">public class Solution {
public boolean exist(char[][] board, String word) {
if (board == null || board.length == 0 || board.length == 0 || word == null) {
return false;
}
int m = board.length, n = board.length;
boolean[][] visited = new boolean;
char[] words = word.toCharArray();
// 遍历矩阵中的每个单元格作为起始点
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (dfs(board, visited, words, i, j, 0)) {
return true;
}
}
}
return false;
}
private boolean dfs(char[][] board, boolean[][] visited, char[] word, int i, int j, int index) {
// 终止条件1:找到完整路径
if (index == word.length) {
return true;
}
// 终止条件2:越界或已访问或字符不匹配
if (i < 0 || i >= board.length || j < 0 || j >= board.length ||
visited || board != word) {
return false;
}
// 标记当前单元格为已访问
visited = true;
// 向四个方向进行DFS搜索
boolean found = dfs(board, visited, word, i + 1, j, index + 1) ||// 向下
dfs(board, visited, word, i - 1, j, index + 1) ||// 向上
dfs(board, visited, word, i, j + 1, index + 1) ||// 向右
dfs(board, visited, word, i, j - 1, index + 1); // 向左
// 回溯:恢复访问状态
visited = false;
return found;
}
}
</code></pre>
<ul>
<li><strong>时间复杂度</strong>:O(3^k × m × n),其中k为单词长度,m、n为矩阵尺寸。每个点有3个方向可选(不能回退)</li>
<li><strong>空间复杂度</strong>:O(k),递归栈深度和visited数组空间</li>
</ul>
<p>时间空间复杂度与方法一相同,但代码更易扩展(如需要八方向移动时只需修改DIRECTIONS数组)</p>
<h3 id="原地标记优化最优">原地标记优化(最优)</h3>
<p>通过修改原矩阵来标记访问状态,节省空间。</p>
<pre><code class="language-java">public class Solution {
public boolean exist(char[][] board, String word) {
if (board == null || board.length == 0 || word == null || word.length() == 0) {
return false;
}
char[] words = word.toCharArray();
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board.length; j++) {
if (dfsOptimized(board, words, i, j, 0)) {
return true;
}
}
}
return false;
}
private boolean dfsOptimized(char[][] board, char[] word, int i, int j, int index) {
// 边界检查和字符匹配检查
if (i < 0 || i >= board.length || j < 0 || j >= board.length ||
board != word) {
return false;
}
// 找到完整路径
if (index == word.length - 1) {
return true;
}
// 原地标记:将当前字符临时替换为特殊标记(不能出现的字符)
char temp = board;
board = '#';// 标记为已访问
// 四个方向搜索
boolean res = dfsOptimized(board, word, i + 1, j, index + 1) ||
dfsOptimized(board, word, i - 1, j, index + 1) ||
dfsOptimized(board, word, i, j + 1, index + 1) ||
dfsOptimized(board, word, i, j - 1, index + 1);
// 回溯:恢复原始字符
board = temp;
return res;
}
}
</code></pre>
<p><strong>关键技巧:</strong></p>
<ol>
<li><strong>临时修改</strong>:将访问过的<code>board</code>改为<code>'#'</code>(或其他不在字母表中的字符)</li>
<li><strong>自动避障</strong>:后续搜索遇到<code>'#'</code>会因字符不匹配而自动跳过</li>
<li><strong>状态恢复</strong>:回溯时恢复原始字符,确保不影响其他路径搜索</li>
</ol>
<p><strong>算法分析:</strong></p>
<ul>
<li><strong>时间复杂度</strong>:O(3^k × m × n),与前述方法相同</li>
<li><strong>空间复杂度</strong>:O(1),<strong>显著优化</strong>!仅使用常数空间(递归栈空间不可避免)</li>
</ul>
</div>
<div id="MySignature" role="contentinfo">
<p>本文来自在线网站:seven的菜鸟成长之路,作者:seven,转载请注明原文链接:www.seven97.top</p><br><br>
来源:https://www.cnblogs.com/sevencoding/p/19495519
頁:
[1]