给定一个二维网格 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(前缀树)。
请先翻阅 LeetCode 单词搜索(图解)
这道题与上一题基本一样,还是使用回溯法但是这道题的数据测试量非常大。
而搜索单词,当单词量时,会存在大量的公共前缀。比如存在单词“aabcd”,“aacad”,在进行搜索时,公共前缀“aa”需要重复搜索。那么有没有一种结构能将它们的公共前缀合并,从而减少搜索次数呢?
答案是显然的,这时就要利用鼎鼎大名**“前缀树”**数据结构来解决重复的问题。
请先翻阅 LeetCode 实现Trie(前缀树)
上面的博客讲解了如何构造一个前缀树。
请先翻阅 LeetCode 添加与搜索单词(基于前缀树)
上面的博客讲解了如何利用前缀树来储存单词。
请先认真阅读上面的博客,下面的代码实现都是基于上面的铺垫。
在此题,我们想将所有的word构建一个前缀树,然后从字符矩阵中进行回溯法搜索。LeetCode 单词搜索(图解)
//前缀树的程序表示
class TrieNode {
public:
bool isWord;//当前节点为结尾是否是字符串
vector<TrieNode*> children;
TrieNode() : isWord(false), children(26, nullptr) {}
~TrieNode() {
for (TrieNode* child : children)
if (child) delete child;
}
};
class Solution {
private:
TrieNode * trieRoot;//构建的单词后缀树
//在树中插入一个单词的方法实现
void addWord(string word) {
TrieNode *ptr = trieRoot;//扫描这棵树,将word插入
//将word的字符逐个插入
for (auto ch : word) {
if (ptr->children[ch - 'a'] == NULL) {
ptr->children[ch - 'a'] = new TrieNode();
}
ptr = ptr->children[ch - 'a'];
}
ptr->isWord = true;//标记为单词
}
public:
int rowSize;//board的行数
int colSize;//board的列数
vector<vector<bool>> boardFlag;//标记board[row][col]是否已使用
//以board[row][col]为中心点,四个方向进行尝试搜索
void dfs(vector<vector<char>>& board, vector<string> &result, string &tempRes, TrieNode * nowRoot, int row, int col) {
if (nowRoot == NULL) {
return;
}
if (nowRoot->isWord) {//如果这个单词成功找到
result.push_back(tempRes);//放入结果
nowRoot->isWord = false;//将这个单词标记为公共后缀 防止重复
}
string tempResAdd;
//上方测试
//如果上方未出界,没有被使用,且nowRoot->children中存在相等的节点
if (row - 1 >= 0 && !boardFlag[row - 1][col] && nowRoot->children[board[row - 1][col] - 'a'] != NULL) {
boardFlag[row - 1][col] = true;//标记使用
tempResAdd = tempRes + char(board[row - 1][col]);
dfs(board, result, tempResAdd, nowRoot->children[board[row - 1][col] - 'a'], row - 1, col);
boardFlag[row - 1][col] = false;//取消标记
}
//下方测试
//如果下方未出界,没有被使用,且nowRoot->children中存在相等的节点
if (row + 1 < rowSize && !boardFlag[row + 1][col] && nowRoot->children[board[row + 1][col] - 'a'] != NULL) {
boardFlag[row + 1][col] = true;//标记使用
tempResAdd = tempRes + char(board[row + 1][col]);
dfs(board, result, tempResAdd, nowRoot->children[board[row + 1][col] - 'a'], row + 1, col);
boardFlag[row + 1][col] = false;//取消标记
}
//左方测试
//如果左方未出界,没有被使用,且nowRoot->children中存在相等的节点
if (col - 1 >= 0 && !boardFlag[row][col - 1] && nowRoot->children[board[row][col - 1] - 'a'] != NULL) {
boardFlag[row][col - 1] = true;//标记使用
tempResAdd = tempRes + char(board[row][col - 1]);
dfs(board, result, tempResAdd, nowRoot->children[board[row][col - 1] - 'a'], row, col - 1);
boardFlag[row][col - 1] = false;//取消标记
}
//右方测试
//如果右方未出界,没有被使用,且nowRoot->children中存在相等的节点
if (col + 1 < colSize && !boardFlag[row][col + 1] && nowRoot->children[board[row][col + 1] - 'a'] != NULL) {
boardFlag[row][col + 1] = true;//标记使用
tempResAdd = tempRes + char(board[row][col + 1]);
dfs(board, result, tempResAdd, nowRoot->children[board[row][col + 1] - 'a'], row, col + 1);
boardFlag[row][col + 1] = false;//取消标记
}
}
vector<string> findWords(vector<vector<char>>& board, vector<string>& words) {
rowSize = board.size();
if (rowSize == 0) {
return {};
}
colSize = board[0].size();
boardFlag = vector<vector<bool>>(rowSize, vector<bool>(colSize, false));//构建标记容器
trieRoot = new TrieNode();//单词后缀树
//将单词都放入前缀树中
for (auto word : words) {
addWord(word);
}
vector<string> result;//用于存储结果
string tempRes;
for (int row = 0; row < rowSize; ++row) {
for (int col = 0; col < colSize; ++col) {
if (trieRoot->children[board[row][col] - 'a'] != NULL) {//搜索
tempRes = "";
tempRes += char(board[row][col]);
boardFlag[row][col] = true;//标记使用
dfs(board, result, tempRes, trieRoot->children[board[row][col] - 'a'], row, col);
boardFlag[row][col] = false;//取消使用
}
}
}
return result;
}
};