LeetCode 79. 单词搜索 回溯题解,巩固回溯写法。

题目

79. 单词搜索

https://leetcode.cn/problems/word-search/

给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。

单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。

思路

回想一下我们人力解的过程:挨个看每个节点,从匹配到的第一个节点开始发散,挨个检查第二个匹配的节点,重复这个过程。

这符合DFS特征。

DFS深度优先

思路

外部起个循环,表示挨个找第一个匹配的点,每次执行dfs,找到一个完整匹配串即可返回。

复杂度分析

时间

最差情况下,我们遍历到最后才匹配到完整的串,那么我们循环了m*n次,每次dfs发散四个方向,由于回溯方向一定被使用过了,那么发散方向可以收敛到三个,实际上小于三,内部dfs深度则为word.len

也就是每个dfs内向三个方向执行word.len次进行串的匹配。

时间复杂度粗略估计:O(m*n*3^word.len)

空间

我们的状态数组used为额外空间,空间复杂度O(m*n)

栈深度O(word.len)

代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public boolean exist(char[][] board, String word) {
    int m = board.length;
    int n = board[0].length;
    // 标记board使用状态
    boolean[][] used = new boolean[m][n];
    for (boolean[] booleans : used) {
        Arrays.fill(booleans, false);
    }
    for (int row = 0; row < m; row++) {
        for (int col = 0; col < n; col++) {
            if (dfs(board, used, word, 0, row, col, m, n)) {
                return true;
            }
        }
    }
    return false;
}

private boolean dfs(char[][] board, boolean[][] used, String word, int wordIdx, int row, int col, int m, int n) {
    if (word.length() == wordIdx) {
        return true;
    }
    if (row < 0 || row > m - 1 || col < 0 || col > n - 1 || used[row][col] || board[row][col] != word.charAt(wordIdx)) {
        return false;
    }

    used[row][col] = true;
    ++wordIdx;

    // @formatter:off
    if (dfs(board, used, word, wordIdx, row, col - 1, m, n)
            || dfs(board, used, word, wordIdx, row, col + 1, m, n)
            || dfs(board, used, word, wordIdx, row - 1, col, m, n)
            || dfs(board, used, word, wordIdx, row + 1, col, m, n)) {
        return true;
    }
    // @formatter:on

    used[row][col] = false;
    --wordIdx;
    return false;
}

写法学习

类似方向发散的代码还可以使用一个集合来配置方向的元数据:

1
2
3
4
5
6
int[][] directions = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};

for (int[] dir : directions) {
    int newi = i + dir[0], newj = j + dir[1];
    // 处理新的坐标
}

剑指 Offer 12. 矩阵中的路径

https://leetcode.cn/problems/ju-zhen-zhong-de-lu-jing-lcof/ 两道题一模一样。

回溯

复杂度分析

同 LeetCode 79 。

代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
public class MatrixPathExist {

    /*
     * 执行用时:
     * 7 ms
     * , 在所有 Java 提交中击败了
     * 22.87%
     * 的用户
     * 内存消耗:
     * 43.4 MB
     * , 在所有 Java 提交中击败了
     * 41.76%
     * 的用户
     * 通过测试用例:
     * 87 / 87
     */

    // 矩阵
    char[][] board;
    // 单词
    String word;
    // 四个方向
    int[][] directions = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
    // 状态标记:单元格是否被访问过
    boolean[][] visited;

    public boolean exist(char[][] board, String word) {
        this.board = board;
        this.word = word;
        this.visited = new boolean[board.length][board[0].length];

        // 从每个位置开始!
        for (int row = 0; row < board.length; row++) {
            for (int col = 0; col < board[row].length; col++) {
                // 如果某个字符开头 dfs 搜索成功,就直接返回
                if (dfs(row, col, 0)) {
                    return true;
                }
            }
        }
        return false;

    }

    /**
     * @param row 当前遍历行
     * @param col 当前遍历列
     * @param i   匹配成功的字符长
     */
    private boolean dfs(int row, int col, int i) {
        int m = board.length;
        int n = board[0].length;

        if (i == word.length()) {
            return true;
        }
        if (outArea(row, m) || outArea(col, n) || board[row][col] != word.charAt(i) || visited[row][col]) {
            return false;
        }

        visited[row][col] = true;
        i++;
        for (int[] direction : directions) {
            int newX = row + direction[0];
            int newY = col + direction[1];
            if (dfs(newX, newY, i)) {
                return true;
            }
        }
        // 状态回撤
        i--;
        visited[row][col] = false;

        return false;
    }

    private boolean inArea(int x, int m) {
        return x >= 0 && x < m;
    }

    private boolean outArea(int x, int m) {
        return !inArea(x, m);
    }
}

Ref