单词搜索 II

单词搜索 II

给定一个 m x n 二维字符网格 board 和一个单词(字符串)列表 words, 返回所有二维网格上的单词 。
单词必须按照字母顺序,通过 相邻的单元格 内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母在一个单词中不允许被重复使用。
在这里插入图片描述
输入:board = [[“o”,“a”,“a”,“n”],[“e”,“t”,“a”,“e”],[“i”,“h”,“k”,“r”],[“i”,“f”,“l”,“v”]], words = [“oath”,“pea”,“eat”,“rain”]
输出:[“eat”,“oath”]

比较好想到的做法就是DFS+回溯。需要注意的是将答案存在一个set中,可以有效地去重。

public class leetcode212 {
    //DFS+回溯  但是这种方式的时间复杂度较高
    Set<String> set = new HashSet<>();

    public List<String> findWords(char[][] board, String[] words) {
        boolean[][] visit = new boolean[board.length][board[0].length];
        for (int i = 0; i < board.length; i++) {
            for (int j = 0; j < board[0].length; j++) {
                for (String word : words) {
                    func(i, j, 0, board, word, visit);
                }
            }
        }
        List<String> ans = new ArrayList<>(set);
        return ans;
    }

    public void func(int i, int j, int index, char[][] board, String word, boolean[][] visit) {
        if (index >= word.length()) return;//剪枝
        if (i >= 0 && i < board.length && j >= 0 && j < board[0].length && !visit[i][j] && board[i][j] == word.charAt(index)) {//剪枝
            visit[i][j] = true;
            if (index == word.length() - 1) {
                set.add(word);
                visit[i][j] = false;
                return;
            }
            func(i + 1, j, index + 1, board, word, visit);
            func(i, j + 1, index + 1, board, word, visit);
            func(i - 1, j, index + 1, board, word, visit);
            func(i, j - 1, index + 1, board, word, visit);
            visit[i][j] = false;//回溯
        }
    }
}

上述的方式复杂度比较高,主要的问题出在:每次都需要对每个word进行比对。如果使用前缀树,就可以实现每次DFS的时候通过比对前缀树,即可比对全部所有的word。

//优化的写法
class solution {
    //回溯+字典树(前缀树)
    int[][] dirs = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
    Set<String> set = new HashSet<>();

    public List<String> findWords(char[][] board, String[] words) {
        //构建前缀树
        Trie2 trie2 = new Trie2();
        for (String word : words) trie2.insert(word);

        for (int i = 0; i < board.length; i++) {
            for (int j = 0; j < board[0].length; j++) {
                func(i,j,board,trie2);
            }
        }
        return new ArrayList<>(set);
    }

    public void func(int i, int j, char[][] board, Trie2 cur) {
        if (board[i][j]=='#') return;//剪枝 说明这个字符已经使用过了
        if (cur.children[board[i][j] -'a'] == null) return;//说明不包含

        char ch = board[i][j];
        cur = cur.children[board[i][j] - 'a'];
        if (cur.isEnd) {
            set.add(cur.str);
            //这里千万别return 因为有可能后面还会有其他字符 就比如words中的字符串是"os","oss".构建前缀树之后,要是return了,os可以,oss没办法找到了。
        }

        board[i][j] = '#';
        for (int[] dir : dirs) {
            int i1 = i + dir[0], j1 = j + dir[1];
            if (i1 >= 0 && i1 < board.length && j1 >= 0 && j1 < board[0].length) {
                func(i1, j1, board, cur);
            }
        }
        board[i][j] = ch;//回溯
    }

}

class Trie2 {
    Trie2[] children;
    boolean isEnd;
    String str;//用于记录字符串结尾时 该字符是谁

    public Trie2() {
        children = new Trie2[26];
    }

    public void insert(String word) {
        Trie2 cur = this;
        for (int i = 0; i < word.length(); i++) {
            if (cur.children[word.charAt(i) - 'a'] == null) {
                cur.children[word.charAt(i) - 'a'] = new Trie2();
            }
            cur = cur.children[word.charAt(i) - 'a'];
        }
        cur.isEnd = true;
        cur.str = word;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值