https://leetcode.com/problems/word-search-ii/description/html
Given a 2D board and a list of words from the dictionary, find all words in the board.算法
Each word must be constructed from letters of sequentially adjacent cell, where "adjacent" cells are those horizontally or vertically neighboring. The same letter cell may not be used more than once in a word.數組
Example:post
Input: words = and board = [ ['o','a','a','n'], ['e','t','a','e'], ['i','h','k','r'], ['i','f','l','v'] ] Output: ["oath","pea","eat","rain"]["eat","oath"]
思路ui
給定一個字符矩陣,判斷按指定規則能不能構成words中的詞。能夠利用DFS來從每一個矩陣位置開始遍歷,窮舉全部的可能。當前字符不在任何word中時要去除掉以免多餘的計算。spa
HashMap
? LinkedList
? "Mutil-LinkedList"
? 最高post的解答給出的方法是用tire,第一次碰到這個概念。搜了下,是單詞查找樹或鍵樹,這種樹形結構:code
好比:orm
每一條邊表示一個字符,若是結束,就用星號表示。在這個Trie結構裏,咱們有下面字符串,好比do, dork, dorm等,可是Trie裏沒有ba, 也沒有sen,由於在a, 和n結尾,沒有結束符號(星號)。htm
能夠用它來保存一個字典,來查詢改字典裏是否有相應的詞。也能夠作智能提示,把用戶已經搜索的詞存在Trie裏,每當用戶輸入一個詞的時候,咱們能夠自動提示,好比當用戶輸入 ba, 咱們會自動提示 bat 和 baii.blog
以上參考:https://www.cnblogs.com/yydcdut/p/3846441.html
如今回到算法題上來,根據給定的words咱們能夠構造一個tire樹,而後去DFS字符矩陣時按照這個樹去匹配。
代碼
public List<String> findWords(char[][] board, String[] words) { List<String> res = new ArrayList<>(); TrieNode root = buildTrie(words); for (int i = 0; i < board.length; i++) { for (int j = 0; j < board[0].length; j++) { dfs (board, i, j, root, res); } } return res; } public void dfs(char[][] board, int i, int j, TrieNode p, List<String> res) { char c = board[i][j]; if (c == '#' || p.next[c - 'a'] == null) return; // 若是當前位置上的字符遍歷過了,或者按當前字符爲索引去下層找節點找不到時 p = p.next[c - 'a']; // if (p.word != null) { // found one,若是p處存了整個word,也就是到了tire樹的一個從上到下路徑的最低端 res.add(p.word); p.word = null; // de-duplicate }
// dfs遍歷以[i,j]處字符開始的全部可能的字符串,按照tire樹判斷字符串是否valid。遍歷中只可能有兩種情形:走到了null或者走到了存了word的末端 board[i][j] = '#'; //從i,j開始dfs時,由於在一個word裏相同的cell不能重用因此要先置爲# if (i > 0) dfs(board, i - 1, j ,p, res); if (j > 0) dfs(board, i, j - 1, p, res); if (i < board.length - 1) dfs(board, i + 1, j, p, res); if (j < board[0].length - 1) dfs(board, i, j + 1, p, res); board[i][j] = c; //遍歷結束後再置回來 } public TrieNode buildTrie(String[] words) { TrieNode root = new TrieNode(); for (String w : words) { // 遍歷words中的全部word, TrieNode p = root; for (char c : w.toCharArray()) { // 遍歷word中的全部字符,好比第一個是 「oath」 int i = c - 'a'; // 將字符與p中的索引一一對應起來(o, a, t, h) if (p.next[i] == null) p.next[i] = new TrieNode(); // p = p.next[i]; // 一趟for下來構造了豎直的 o-a-t-h 這樣的tire樹,只不過如今每一個節點上的word是空的, } p.word = w; // p停在末位處(h),而且存儲了整個word("oath") } return root; }
class TrieNode { // 記錄了當前節點中的word,以及下一層的全部節點,節點是根據數組索引來取,而數組索引能夠根據字符直接獲得,換句話說就是直接經過字符取節點 TrieNode[] next = new TrieNode[26]; // 每一個節點下面一層最多有26個子節點,由於字母只有26個,這裏經過索引來肯定下層的字符,因此就不須要額外存儲字符了 String word; // 這裏的tire樹仍是有點不同的 }
這裏樹的構造有點難理解,這裏是定每一個tire節點下面有26個節點,索引分別是a~z,而後初始全是null。若是每一個字符是在當前word上時代表字符合法,那麼將按照這個字符來索引到的節點初始化,只要使其不爲null便可。以字符串 oath 爲例,遍歷其全部字符,從根節點開始,第一個字符是 o,如今知道根節點下層中的一個是字符o, 因此將根節點中的next數組在索引o處的TrieNode初始化,o的下個字符是a,那麼將o的next數組中的o索引處的TrieNode初始化,一步步這樣來,最後就構成了從根節點到索引爲字符h的TrieNode的路徑,路徑上的節點不爲空便可。最後在索引爲h的TrieNode中存儲整個word (「oath」)。