回溯-单词搜索

在二维数组进行单词搜索也是经典的需要采用回溯算法的问题。

案例1:

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

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

 

 这个题目里的二维字符只有4X3=12个,肉眼基本上就能搜索到有没有可行的解。

但是如果数组变成了10X10,甚至更大,用肉眼肯定是不合适了。

我们需要把自己的经验,让计算机帮我们搜索。

这个题目我的思路如下:

一、最外层是广度优先搜索
从i=0,j=0开始依次判断是否有一个字符board[i][j]和目标字符串的第一个字符word[0]相等,不相等的话,考虑下一个位置。
下一个位置就是是行从上到下,列从左到右,没有什么特别的。
具体实现请见nextBreadthPos
使用一个Boolean值res记录是否曾经成功匹配过,如果res为true,则可以直接跳出最外层遍历,返回res,否则一直遍历到二维数组的最右下角的位置,返回false

二、内层是一个深度优先搜索
当最外层的遍历匹配上第一个字符word[0]之后,我们相当于找到一个破案线索,接下来就是在这个线索周围进行深层搜索,这个nextPos有多种选择,具体实现在nextDepthPos方法中,因为对于角和边的位置,nextPos的数量不同,所有我是有了一个List来存储这些位置。
需要考虑的一个问题是nextPos其实已经包含了我们曾经遍历过的位置,所以对应每一次的深度优先遍历,我们都需要有一个used数组,来记录曾经访问的位置,这些位置不需要再访问。
当在dfs中又找到一个目标新的字符时,我们对pos加1,更新used数组,然后继续深入

三、递归的边界
第二部分是正常的深度优先搜索的思路,还有一个没有考虑的问题是如果所有的相邻位置都遍历过,没有找到目标字符,下一步该怎么办?
显而易见,改进行回溯了,当前字符会被放弃,used会被重置为false,pos也会减1.
所有dfs方法有两个return的地方,一个在明处,一个在暗处,在暗处的就是for循环结束的时候,没有继续深入,就需要向上return了。

class Solution {
   int rowLen;
    int colLen;
    boolean res;

    public boolean exist(char[][] board, String word) {
        rowLen = board.length;
        colLen = board[0].length;
        bfs(board, word);
        return res;
    }

    /**
     * 最外层是广度优先搜索,需要变量二维数组中的所有元素
     *
     * @param board
     * @param word
     */
    private void bfs(char[][] board, String word) {
        int i = 0, j = 0, pos = 0;
        boolean[][] used = new boolean[rowLen][colLen];
        while (i < rowLen && j < colLen) {
            if (res) {
                break;
            }
            if (board[i][j] == word.charAt(pos)) {
                clearArray(used);
                used[i][j] = true;
                //[i,j]匹配上了pos
                dfs(board, word,  used, i, j, pos);
            }
            int[] nextBfsPos = nextBreadthPos(i, j);
            i = nextBfsPos[0];
            j = nextBfsPos[1];
        }
    }

    /**
     * 第一个字符匹配上之后开始进行深度优先搜索
     * 递归弹出的条件是最后一个字符的所有周边字符都不能匹配目标字符
     *
     * @param board
     * @param word
     * @param colStart dfs的起始位置
     * @param rowStart dfs的起始位置
     * @param pos 已经匹配上的字符索引位置
     */
    private void dfs(char[][] board, String word,  boolean[][] used, int rowStart, int colStart, int pos) {
        if (pos+1 == word.length()) {
            res = true;
            return;
        }
        //next数组没有考虑已经遍历过的路径
        List<int[]> nextDepthPos = nextDepthPos(rowStart, colStart);
        for (int[] nextDepthPo : nextDepthPos) {
            int i1 = nextDepthPo[0];
            int j1 = nextDepthPo[1];
            int pos1 = pos + 1;
            if (used[i1][j1]) {
                continue;
            }
            if (board[i1][j1] == word.charAt(pos1)) {
                used[i1][j1] = true;
                dfs(board, word,  used, i1, j1, pos1);
                used[i1][j1] = false;
            }
        }


    }

    /**
     * 广度优先搜索的下一个位置
     *
     * @param i
     * @param j
     * @return
     */
    private int[] nextBreadthPos(int i, int j) {
        if (j == colLen - 1) {
            return new int[]{i + 1, 0};
        } else {
            return new int[]{i, j + 1};
        }
    }

    /**
     * 深度优先搜索的下一个位置
     *
     * @param i
     * @param j
     * @return
     */
    private List<int[]> nextDepthPos(int i, int j) {

        List<int[]> list = new ArrayList<>();
        if (i + 1 < rowLen) {
            list.add(new int[]{i + 1, j});
        }
        if (j + 1 < colLen) {
            list.add(new int[]{i, j + 1});
        }
        if (i - 1 >= 0) {
            list.add(new int[]{i - 1, j});
        }
        if (j - 1 >= 0) {
            list.add(new int[]{i, j - 1});
        }
        return list;
    }

    private void clearArray(boolean[][] arr) {
        for (boolean[] ar : arr) {
            Arrays.fill(ar, false);
        }
    }



}

 

posted @ 2022-11-01 18:40  Mars.wang  阅读(42)  评论(0编辑  收藏  举报