leetcode 212. Word Search II(单词搜索 II)

给出一个2D板和字典中的单词列表,找出所有同时在2D板和字典中出现的单词。

每个单词必须由顺序相邻单元的字母构成,其中“相邻”单元是那些水平或垂直相邻的单元。同一个字母单元在一个单词中可能不会多次使用。

例如, 给出 words = [“oath”,”pea”,”eat”,”rain”] 和 board =

[
[‘o’,’a’,’a’,’n’],
[‘e’,’t’,’a’,’e’],
[‘i’,’h’,’k’,’r’],
[‘i’,’f’,’l’,’v’]
]
返回 [“eat”,”oath”]。

注意: 您可以假设所有输入都由小写字母 a-z 组成。

你需要优化回溯算法以通过更大数据量的测试。你能否早点停止回溯?

如果当前单词不存在所有单词的前缀中,则可以立即停止回溯。什么样的数据结构可以有效地执行这样的操作?散列表是否可行?为什么? Trie(前缀树) 如何?如果你想学习如何实现一个基本的前缀树,请先处理这个问题: 实施Trie(前缀树)。

这道题目最容易想到的就是穷举法:
找到所有匹配目标字符串的首字母,然后往相邻位置寻找,寻找的时候可以在2D板中把已经找到的字符标记,避免重复使用同一位置字符。

List<String> list = new ArrayList<String>();
    public List<String> findWords(char[][] board, String[] words) {
        list = new ArrayList<String>();

        if(board.length > 0 && board[0].length > 0 && words.length > 0){
            for(int i=0;i<board.length;i++){
                for(int j=0;j<board[0].length;j++){
                    for(int k=0;k<words.length;k++){
                        if(board[i][j] == words[k].charAt(0)){
                            char c = board[i][j];
                            board[i][j] = '1';
                            findWords2(board,i,j, words[k] , words[k].substring(1));
                            board[i][j] = c;
                        }
                    }

                }
            }

            return list;
        }
        return list;

    }
    public boolean findWords2(char[][] board,int i,int j, String word,String remain) {
        if(remain.equals("") ){
            if(!list.contains(word)){
                list.add(word);
            }
            return true;
        }
        if(i-1 > -1 && board[i-1][j] != '1' && board[i-1][j] == remain.charAt(0)){
            char c = board[i-1][j];
            board[i-1][j] = '1';
            if(findWords2(board,i-1, j, word, remain.substring(1))){
                board[i-1][j] = c;
                return true;
            }
            board[i-1][j] = c;
        }
        if(i+1 < board.length && board[i+1][j] != '1' && board[i+1][j] == remain.charAt(0)){
            char c = board[i+1][j];
            board[i+1][j] = '1';
            if(findWords2(board,i+1, j, word, remain.substring(1))){
                board[i+1][j] = c;
                return true;
            }
            board[i+1][j] = c;
        }
        if(j-1 > -1 && board[i][j-1] != '1' && board[i][j-1] == remain.charAt(0)){
            char c = board[i][j-1];
            board[i][j-1] = '1';
            if(findWords2(board,i, j-1, word, remain.substring(1))){
                board[i][j-1] = c;
                return true;
            }
            board[i][j-1] = c;
        }
        if(j+1 < board[0].length && board[i][j+1] != '1' && board[i][j+1] == remain.charAt(0)){
            char c = board[i][j+1];
            board[i][j+1] = '1';
            if(findWords2(board,i, j+1, word, remain.substring(1))){
                board[i][j+1] = c;
                return true;
            }
            board[i][j+1] = c;
        }
        return false;
    }

逻辑比较简单,但是效率不高,806 ms。

  1. 提示中提到了前缀树,那么可以考虑先实现前缀树,再通过前缀匹配达到剪枝目的。
  2. 这么做的好处是一次把所有目标单词纳入树中,回溯时可以与所有目标单词做匹配。
  3. 关于前缀树的实现也比较简单,由于限制值出现小写字母,那么用一颗每个节点最多26个子节点的树来实现,需要注意的地方是每次insert完成时应该在当前节点标记一下isend,表示此整个单词在树中,到时匹配单词的search()需要用到
  4. 而insert和startsWith只需要遍历trie就好了,差别只在于insert边遍历边插入
class Solution {
    class TrieNode{  
        TrieNode next[] = new TrieNode[26];
        public boolean isEnd;
        public TrieNode(){}  

    }  
    class Trie {
        private TrieNode head;

        /** Initialize your data structure here. */
        public Trie() {
            head = new TrieNode();
        }
        public boolean search(String word) {
            if(word == null || word.length() < 1){
                return true;    
            }
            if(head.next[word.charAt(0)-'a'] == null){
                return false;
            }
            TrieNode cur = head.next[word.charAt(0)-'a'];
            for(int i=1;i<word.length();i++){
                if(cur.next[word.charAt(i)-'a'] == null){
                    return false; 
                }
                cur = cur.next[word.charAt(i)-'a'];
            }
            if(cur.isEnd){
                return true;
            }
            return false; 
        }
        /** Inserts a word into the trie. */
        public void insert(String word) {
            if(word != null && word.length() > 0 && head.next[word.charAt(0)-'a'] == null){
                head.next[word.charAt(0)-'a'] = new TrieNode();
            }
            TrieNode cur = head.next[word.charAt(0)-'a'];
            for(int i=1;i<word.length();i++){
                if(cur.next[word.charAt(i)-'a'] == null){
                    cur.next[word.charAt(i)-'a'] = new TrieNode(); 
                }
                cur = cur.next[word.charAt(i)-'a'];
            }
            cur.isEnd = true;
        }

        /** Returns if there is any word in the trie that starts with the given prefix. */
        public boolean startsWith(String prefix) {
            if(prefix == null || prefix.length() < 1){
                return true;    
            }
            if(head.next[prefix.charAt(0)-'a'] == null){
                return false;
            }
            TrieNode cur = head.next[prefix.charAt(0)-'a'];
            for(int i=1;i<prefix.length();i++){
                if(cur.next[prefix.charAt(i)-'a'] == null){
                    return false; 
                }
                cur = cur.next[prefix.charAt(i)-'a'];
            }
            return true;
        }
    }


    Set<String> set = new HashSet<String>();
    public List<String> findWords(char[][] board, String[] words) {
        set.clear();
        List<String> resList = new ArrayList<String>();
        if(board.length > 0 && board[0].length > 0 && words.length > 0){
            Trie t = new Trie();
            for(int i=0;i<words.length;i++){
                t.insert(words[i]);
            }
            for(int i=0;i<board.length;i++){
                for(int j=0;j<board[0].length;j++){
                    char c = board[i][j];
                    board[i][j] = '1';
                    findWords2(board,i,j, t, c+"");
                    board[i][j] = c;
                }
            }

            for(int i=0;i<words.length;i++){
                if(set.contains(words[i]) && !resList.contains(words[i])){
                    resList.add(words[i]);
                }
            }

            return resList;
        }
        return resList;

    }
    public void findWords2(char[][] board,int i,int j,Trie t,String curStr) {
        if(t.search(curStr)){
            set.add(curStr);
        }
        if(t.startsWith(curStr)){
            if(i-1 > -1 && board[i-1][j] != '1'){
                char c = board[i-1][j];
                board[i-1][j] = '1';
                findWords2(board,i-1, j, t, curStr+c);
                board[i-1][j] = c;
            }
            if(i+1 < board.length && board[i+1][j] != '1'){
                char c = board[i+1][j];
                board[i+1][j] = '1';
                findWords2(board,i+1, j, t, curStr+c);
                board[i+1][j] = c;
            }
            if(j-1 > -1 && board[i][j-1] != '1'){
                char c = board[i][j-1];
                board[i][j-1] = '1';
                findWords2(board,i, j-1, t, curStr+c);
                board[i][j-1] = c;
            }
            if(j+1 < board[0].length && board[i][j+1] != '1'){
                char c = board[i][j+1];
                board[i][j+1] = '1';
                findWords2(board,i, j+1, t, curStr+c);
                board[i][j+1] = c;
            }
        }
        return ;
    }
}

用前缀树直接将用时减少到71ms

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值