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.
For example,
Given words = ["oath","pea","eat","rain"]
and board =
[
['o','a','a','n'],
['e','t','a','e'],
['i','h','k','r'],
['i','f','l','v']
]
Return ["eat","oath"]
.
Note:
You may assume that all inputs are consist of lowercase letters a-z
.
分析
參照Word Search梁只,我先嘗試使用類似的方法缚柳。由于查詢單詞需要從board的某個坐標點(i, j)出發(fā),如果查詢每個單詞都得遍歷一遍board顯然效率太低敛纲。因此我使用一個哈希表紀錄了board中不同字母起點的坐標喂击,比如例子中‘a’
的起點有[[0,1],[0,2],[1,2]]
。由此淤翔,在查詢過程中可以直接從哈希表中取起點翰绊,無需遍歷board漠魏。
但最后這個算法Time Limit Exceeded
加匈,原因如下:對于相同前綴的words中的若干個單詞麦撵,我們機械地查找了多次音五,實際上只需要查詢一次即可坚嗜。如["aaaaaa", "aaaaab", "aaaaac"]
,它們都具有相同的前綴“aaaaa”
。改進方法后面再分析,先給出這個TLE的算法:
TLE代碼
class Solution {
public:
vector<string> findWords(vector<vector<char>>& board, vector<string>& words) {
vector<string> answer;
int row = board.size();
int col = board[0].size();
vector<vector<pair<int, int>>> store(26);
for (int i = 0; i != row; ++i) {
for (int j = 0; j != col; ++j) {
int index = find(board[i][j]);
store[index].push_back(make_pair(i, j));
}
}
for (string word : words) {
if (!word.size()) {
answer.push_back("");
break;
}
int index = find(word[0]);
for (auto pair : store[index]) {
if (search(board, pair.first, pair.second, word, 0)) {
answer.push_back(word);
}
}
}
return answer;
}
int find(char letter) { return static_cast<int> (letter - 'a'); }
bool search(vector<vector<char>>& board, int i, int j, string word, int k) {
if (++k == word.size()) return true;
board[i][j] = 'X';
int dx[] = {1, 0, -1, 0};
int dy[] = {0, 1, 0, -1};
for (int s = 0; s != 4; ++s) {
int new_i = i + dy[s], new_j = j + dx[s];
if (inBoard(board, new_i, new_j)
&& board[new_i][new_j] == word[k]
&& search(board, new_i, new_j, word, k)) {
board[i][j] = word[--k];
return true;
}
}
board[i][j] = word[--k];
return false;
}
bool inBoard(vector<vector<char>>& board, int i, int j) {
int row = board.size(), col = board[0].size();
return i < row && i >= 0 && j < col && j >= 0;
}
};
正如上面分析的那樣午笛,這種基于哈希表的算法癌佩,每次只查詢一個單詞放案,顯然這會導致重復工作。所以正確的做法是構造一個字典樹(Trie Tree),將字典樹作為一個待查詢的字符串集,在board中進行查找。
毋庸置疑,基于字典樹的算法每次都查詢整個字符串集枕稀,避免了相同前綴多次搜索的問題沐兰。字典樹的實現請見Leetcode208-Implement Trie (Prefix Tree)。
這里我在Leetcode208的基礎上做了一些變化:
- 不使用
bool
類型的isTail
來標識word
的末尾。而用string*
指針指向words
中的單詞。原因在于,當知道到達一個待查詢單詞的末尾時,需要將它加入answer
中,顯然isTail
只能告訴我們到了末尾,卻不能告訴我們單詞是什么。 - 本題構造的字典樹不需要
search(key)
方法。因為此處的字典樹是待查詢的字符串集,而非被查詢的字典。 - 使用了新的c++語法。
- 搜索路徑上同一個字母不能多次使用。因此經過一個
board
上的節(jié)點贤旷,都將其標記為‘X’
盅藻,因為本題承諾所有的輸入都是小寫字母假残,所以這樣沒有問題眶俩。在遞歸結束時需要將board
上標記為'X'
的點都復原线罕。
AC代碼
int find(char letter) { return static_cast<int> (letter - 'a'); }
class TrieNode {
public:
string * wordLocation;
TrieNode * letters[26];
TrieNode() {
wordLocation = NULL;
for (int i = 0; i != 26; ++i) { letters[i] = NULL; }
}
};
class WordsDictionary {
public:
WordsDictionary(): root(new TrieNode()) {}
void addWord(string& word) {
TrieNode * curr = root;
for (int i = 0; i != word.size(); ++i) {
int index = find(word[i]);
if (!curr->letters[index]) {
curr->letters[index] = new TrieNode();
}
curr = curr->letters[index];
}
curr->wordLocation = &word;
}
TrieNode* root;
};
class Solution {
public:
vector<string> findWords(vector<vector<char>>& board, vector<string>& words) {
vector<string> answer;
WordsDictionary wordsDict;
unordered_set<string> check;
int row = board.size(), col = board[0].size();
for (string& word : words) {
wordsDict.addWord(word);
}
for (int i = 0; i != row; ++i) {
for (int j = 0; j != col; ++j) {
findWordsInBoard(board, i, j, wordsDict.root, answer, check);
}
}
return answer;
}
void findWordsInBoard(vector<vector<char>>& board, int i, int j, TrieNode * curr, vector<string>& answer, unordered_set<string>& check) {
if (!inBoard(board, i, j) || board[i][j] == 'X') return;
char currentLetter = board[i][j];
int dx[] = {1, 0, -1, 0};
int dy[] = {0, 1, 0, -1};
int index = find(currentLetter);
board[i][j] = 'X';
if (curr->letters[index]) {
string * location = curr->letters[index]->wordLocation;
if (location && check.find(*location) == check.end()) {
answer.push_back(*curr->letters[index]->wordLocation);
check.insert(*location);
}
for (int k = 0; k != 4; ++k) {
findWordsInBoard(board, i + dy[k], j + dx[k], curr->letters[index], answer, check);
}
}
board[i][j] = currentLetter;
}
bool inBoard(vector<vector<char>>& board, int i, int j) {
int row = board.size();
int col = board[0].size();
return i >= 0 && i < row && j >= 0 && j < col;
}
};