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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章