212. Word Search II:多个单词查找

写在前面:这两周持续看花花酱整理的题目列表视频讲解,也得益于自己持续多年刷题,今天刷这道题目的想法是:会trie树居然就能攻克hard题目!我也离独立攻破hard题目不远了嘛。前段时间看王争在极客时间的系列课程,trie树是不在话下的。好,开始写题。

题目描述

输入:二维字符数组,做棋盘baord;字符串列表words。
返回:可以在棋盘中找到的所有单词word。
规则:每个单词需要按顺序在棋盘中匹配,棋盘匹配的时候只能移动相邻位置。相邻位置是指上下左右。
例子:输入
board = [
[‘o’,‘a’,‘a’,‘n’],
[‘e’,‘t’,‘a’,‘e’],
[‘i’,‘h’,‘k’,‘r’],
[‘i’,‘f’,‘l’,‘v’]
]
words = [“oath”,“pea”,“eat”,“rain”]

输出: [“eat”,“oath”]

暴力回溯

按照回溯法,每次解决一个单词word。可以参考题目79
参考例子中,board的行列:m=4,n=4。查找单词word=oath。dfs搜索的时候每找到单词中的一个字符作为一层。在每一层向下一层移动过程中有上、下、左、右4种选择。
枚举每一个位置(i,j)作为起始查找位置。因为不确定哪个位置的ch与word的第一个字符相同。

dfs(i,j,index),i:第i行;j:第j列;index:单词中的第index个字符。
如果 board[i][j]=word[index],则设置board[i][j]=’#’,防止重复查找,继续向4个方向遍历。
当index=word.length的时候,表示word是存在的,加入结果集。
当数组下标i,j越界,或者重复查找的时候返回。
如果board[i][j]!=word[index],则返回,不继续搜索。

最终每个单词查找一次,返回结果。
时间复杂度:$ O ( m ∗ n ∗ 4 l ) O(m*n*4^l) O(mn4l),l=单词长度

class Solution {
    private char[][] board;
    private String word;
    private int m;
    private int n;
    public List<String> findWords(char[][] board, String[] words) {
        this.board = board;
        m = board.length;
        if(m == 0) return new ArrayList<String>();
        n = board[0].length;
        List<String> result = new ArrayList<String>();
        for(String word : words){
            if(findWord(word)){
                result.add(word);
            }
        }
        return result;
    }
    
    private boolean findWord(String word) {
        this.word = word;
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                if(dfs(i,j,0)){
                    return true;
                }
            }
        }
        return false;
    }
    
    private boolean dfs(int i,int j,int index){
        if(index == word.length()) return true;
        if(i<0 || i>=m || j<0 || j>=n || board[i][j]=='#'){
            return false;
        }
        boolean r = false;
        if(board[i][j] == word.charAt(index)){
            char ch = board[i][j];
            board[i][j] = '#';
            r = dfs(i-1,j,index+1)
                    || dfs(i+1,j,index+1)
                    || dfs(i,j-1,index+1)
                    || dfs(i,j+1,index+1);
            board[i][j] = ch;
        }
        return r;
    }
}

Trie树

这里需要查找多个单词。例如word=[“baa”,“bab”,“aaab”,“aaa”,“aaaa”]。单词baa和bab有公共前缀ba,暴力回溯每次都从0开始查找,浪费时间。如果能查找到baa之后回溯一步,继续查找到bab就好了。单词aaa和aaaa,也是同样的情况。这里就需要用到Trie树。Trie树在有公共前缀子串的情况下,会极大的降低时间复杂度。Trie树学习可以参考我的博客
在这里插入图片描述

上图展示了构建完成的Trie树结构。这里在每个单词结束位置添加了word表示当前字符串的值。便于记录结果。

和暴力回溯类似,遍历每一个起始位置(i,j),从根节点开始查找。
dfs(i,j,node):每次查找到一个节点,word不为空,说明找到了一个字符串,加入结果集并且word设置为null。
数组下标i,j越界,或者重复访问,则返回。
如果node.children[board[i][j]-‘a’]不为空,则继续朝4个方向搜索。否则返回。

时间复杂度:构建trie树的时间复杂度是所有单词长度和:$O(sum(l))。

搜索过程,因为有合并搜索,所以最深的深度是max(l)。在每一层会查找4个方向,所以是 O ( 4 m a x ( l ) ) O(4^{max(l)}) O(4max(l))

所以最终时间负责度是: O ( s u m ( l ) + 4 m a x ( l ) ) O(sum(l)+4^{max(l)}) O(sum(l)+4max(l))

class Solution {
    private char[][] board;
    private String word;
    private int m;
    private int n;
    private TrieNode root;
    private List<String> result;
    public List<String> findWords(char[][] board, String[] words) {
        this.board = board;
        m = board.length;
        if(m == 0) return new ArrayList<String>();
        n = board[0].length;
        result = new ArrayList<String>();
        buildTree(words);
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                dfs(i,j,root);
            }
        }
        return result;
    }
    
    private void dfs(int i,int j,TrieNode node){
        if(node.word!=null){
            result.add(node.word);
            node.word = null;
        }
        if(i<0 || i>=m || j<0 || j>=n || board[i][j]=='#'){
            return;
        }        
        
        char ch = board[i][j];
        if(node.children[ch-'a']==null) return;
        TrieNode n = node.children[ch-'a'];
        board[i][j] = '#';
        dfs(i-1,j,n);
        dfs(i+1,j,n);
        dfs(i,j-1,n);
        dfs(i,j+1,n);
        board[i][j] = ch;
    }
    
    private void buildTree(String[] words){
        root = new TrieNode('/');
        for(String word : words){
            addWord(word);
        }
    }
 
    public void addWord(String word){
        TrieNode p = root;
        for(int i=0;i<word.length();i++){
            int idx = word.charAt(i)-'a';
            if(p.children[idx]==null){
                p.children[idx] = new TrieNode(word.charAt(i));
            }
            p = p.children[idx];
        }
        p.end = true;
        p.word = word;
    }
    
    class TrieNode{
        private char data;
        private boolean end;
        private TrieNode[] children = new TrieNode[26];
        private String word;
        public TrieNode(char data){
            this.data = data;
        }

        public boolean isEnd(){
            return end;
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值