給出一個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。
- 提示中提到了前綴樹,那麼可以考慮先實現前綴樹,再通過前綴匹配達到剪枝目的。
- 這麼做的好處是一次把所有目標單詞納入樹中,回溯時可以與所有目標單詞做匹配。
- 關於前綴樹的實現也比較簡單,由於限制值出現小寫字母,那麼用一顆每個節點最多26個子節點的樹來實現,需要注意的地方是每次insert完成時應該在當前節點標記一下isend,表示此整個單詞在樹中,到時匹配單詞的search()需要用到
- 而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