给定一个二维网格 board 和一个字典中的单词列表 words,找出所有同时在二维网格和字典中出现的单词。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母在一个单词中不允许被重复使用。
示例:
输入: words =["oath","pea","eat","rain"]
and board = [ ['o','a','a','n'], ['e','t','a','e'], ['i','h','k','r'], ['i','f','l','v'] ] 输出:["eat","oath"]
说明:
你可以假设所有输入都由小写字母 a-z
组成。
提示:
- 你需要优化回溯算法以通过更大数据量的测试。你能否早点停止回溯?
- 如果当前单词不存在于所有单词的前缀中,则可以立即停止回溯。什么样的数据结构可以有效地执行这样的操作?散列表是否可行?为什么? 前缀树如何?如果你想学习如何实现一个基本的前缀树,请先查看这个问题: 实现Trie(前缀树)。
做这道题前最好先做字典树和单词搜索1.
字典树+DFS。
最先想的是将搜索过程中能组成的所有单词全部存到字典树中,结果超时了。
后来一想这样的方法太笨效率太低了,而且很浪费空间,字典树存了太多没有出现过的单词。
但是如果字典树只存要我们搜索的单词,然后再去搜索它就能节约不少的性能和空间了。
其实就是将上面的方法反过来再修改一下。
另外还要注意的是出现重复的答案和没有搜索完全的答案。
超时:
class Trie
{
public:
struct TrieNode
{
vector<TrieNode *> children;
TrieNode()
{
children = vector<TrieNode *>(26, NULL);
}
~TrieNode()
{
for (int i = 0; i < 26; i++)
{
if (children[i] != NULL)
{
delete children[i];
}
}
}
};
TrieNode *root;
Trie()
{
root = new TrieNode();
}
~Trie()
{
delete root;
}
bool find(string word, TrieNode *&newRoot)
{
for (int i = 0; i < word.size(); i++)
{
if (newRoot->children[word[i] - 'a'] == NULL)
{
return false;
}
newRoot = newRoot->children[word[i] - 'a'];
}
return true;
}
void insert(string word)
{
TrieNode *newRoot = root;
for (int i = 0; i < word.size(); i++)
{
if (newRoot->children[word[i] - 'a'] == NULL)
{
newRoot->children[word[i] - 'a'] = new TrieNode();
}
newRoot = newRoot->children[word[i] - 'a'];
}
}
bool startsWith(string prefix)
{
TrieNode *newRoot = root;
return find(prefix, newRoot);
}
};
class Solution
{
Trie* t;
int dx[4] = {1, -1, 0, 0};
int dy[4] = {0, 0, 1, -1};
int borderRow;
int borderCol;
vector<string> ans;
public:
vector<string> findWords(vector<vector<char>> &board, vector<string> &words)
{
t = new Trie();
borderRow = board.size();
if(borderRow == 0 || words.size() == 0)
{
return ans;
}
borderCol = board[0].size();
vector<vector<bool>> visit(borderRow, vector<bool>(borderCol, false));
map<string, bool> check;
string str = "";
for (int i = 0; i < words.size(); i++)
{
for (int r = 0; r < borderRow; r++)
{
for (int c = 0; c < borderCol; c++)
{
if(board[r][c] == words[i][0])
{
if(check[words[i]] == true)
continue;
if (t->startsWith(words[i]))
{
//cout << r << c << " 1" << words[i] << endl;
check[words[i]] = true;
ans.push_back(words[i]);
}
else
{
visit[r][c] = true;
if (DFS(board, visit, r, c, str + board[r][c], words[i]))
{
//cout << r << c <<" 2" << words[i] << endl;
check[words[i]] = true;
ans.push_back(words[i]);
}
visit[r][c] = false;
}
}
}
}
}
return ans;
}
bool DFS(vector<vector<char>> &board, vector<vector<bool>> &visit, int row, int col, string str, string word)
{
if(str.size() >= word.size())
{
//cout << str << endl;
t->insert(str);
if(str == word)
return true;
return false;
}
for (int i = 0; i < 4; i++)
{
int newRow = row + dx[i];
int newCol = col + dy[i];
if(newRow < 0 || newRow >= borderRow || newCol < 0 || newCol >= borderCol)
continue;
if(visit[newRow][newCol])
continue;
visit[newRow][newCol] = true;
if(DFS(board, visit, newRow, newCol, str + board[newRow][newCol], word))
{
visit[newRow][newCol] = false;
return true;
}
visit[newRow][newCol] = false;
}
return false;
}
};
正确答案:
class Solution
{
public:
struct TrieNode
{
vector<TrieNode *> children;
bool isWord;
TrieNode()
{
children = vector<TrieNode *>(26, NULL);
isWord = false;
}
~TrieNode()
{
for (int i = 0; i < 26; i++)
{
if (children[i] != NULL)
{
delete children[i];
}
}
}
};
class Trie
{
public:
TrieNode *root;
Trie()
{
root = new TrieNode();
}
~Trie()
{
delete root;
}
void insert(string word)
{
TrieNode *newRoot = root;
for (int i = 0; i < word.size(); i++)
{
if (newRoot->children[word[i] - 'a'] == NULL)
{
newRoot->children[word[i] - 'a'] = new TrieNode();
}
newRoot = newRoot->children[word[i] - 'a'];
}
newRoot->isWord = true;
}
};
void DFS(vector<vector<char>> &board, vector<vector<bool>> &visit, TrieNode *newRoot, int row, int col, string str)
{
if (newRoot == NULL)
return;
if (newRoot->isWord)
{
//标记为false,防止出现重复的答案
newRoot->isWord = false;
ans.push_back(str);
//注意如果这里return的话,某些案例下会少答案,原因可想而知
}
for (int i = 0; i < 4; i++)
{
int newRow = row + dx[i];
int newCol = col + dy[i];
if (newRow < 0 || newRow >= borderRow || newCol < 0 || newCol >= borderCol)
continue;
if (visit[newRow][newCol])
continue;
if (newRoot->children[board[newRow][newCol] - 'a'] == NULL)
continue;
visit[newRow][newCol] = true;
DFS(board, visit, newRoot->children[board[newRow][newCol] - 'a'], newRow, newCol, str + board[newRow][newCol]);
visit[newRow][newCol] = false;
}
}
vector<string> findWords(vector<vector<char>> &board, vector<string> &words)
{
t = new Trie();
borderRow = board.size();
if (borderRow == 0 || words.size() == 0)
{
return ans;
}
borderCol = board[0].size();
//标记数组,将走过了路程标记
vector<vector<bool>> visit(borderRow, vector<bool>(borderCol, false));
map<string, bool> check;
string str = "";
for(string s : words)
{
t->insert(s);
}
for (int r = 0; r < borderRow; r++)
{
for (int c = 0; c < borderCol; c++)
{
if (t ->root ->children[board[r][c] - 'a'] != NULL)
{
visit[r][c] = true;
TrieNode *newRoot = t->root->children[board[r][c] - 'a'];
DFS(board, visit, newRoot, r, c, str + board[r][c]);
visit[r][c] = false;
}
}
}
return ans;
}
private:
//字典树
Trie *t;
//DFS所用方向数组
int dx[4] = {1, -1, 0, 0};
int dy[4] = {0, 0, 1, -1};
//边界
int borderRow;
int borderCol;
//答案
vector<string> ans;
};