思路:
使用回溯法进行深度遍历同时判断字符串。
1.原始的回溯法,通过限制字符串长度作为判断条件,会超时,需要优化判断方式
2.使用字典树来判断目前遍历的字符串是否满足前缀条件,如果满足就直接剪枝,提高了效率
原题链接:https://leetcode.cn/problems/word-search-ii/description/?favorite=2ckc81c
参考答案:https://leetcode.cn/problems/word-search-ii/solutions/1000172/dan-ci-sou-suo-ii-by-leetcode-solution-7494/
1.题目如下:
给定一个 m x n 二维字符网格 board 和一个单词(字符串)列表 words, 返回所有二维网格上的单词 。
单词必须按照字母顺序,通过 相邻的单元格 内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母在一个单词中不允许被重复使用。
示例 1:
输入:board = [["o","a","a","n"],["e","t","a","e"],["i","h","k","r"],["i","f","l","v"]], words = ["oath","pea","eat","rain"]
输出:["eat","oath"]
示例 2:
输入:board = [["a","b"],["c","d"]], words = ["abcb"]
输出:[]
提示:
m == board.length
n == board[i].length
1 <= m, n <= 12
board[i][j]是一个小写英文字母
1 <= words.length <= 3 * 104
1 <= words[i].length <= 10
words[i] 由小写英文字母组成
words 中的所有字符串互不相同
2.代码如下:
class Solution {
private:
unordered_set<string> setTemp;
vector<vector<int>> pos={{0,1},{1,0},{-1,0},{0,-1}};
int maxl=0;
public:
//思路一:回溯法 超时
/*
通过深度优先遍历和回溯法来遍历单词,用mark标记使用过的字符
通过set数组来判断单词是否有效效率更高
超时; 需要用前缀树来减少无效的遍历
*/
/*
vector<string> findWords(vector<vector<char>>& board, vector<string>& words) {
//set作为判断依据
for(int i=0;i<words.size();i++){
setTemp.insert(words[i]);
}
//标记防止重复遍历
vector<vector<int>> mark(board.size(),vector<int>(board[0].size(),0));
string temp;
vector<string> ans;
// 最大长度 ,可以作为退出遍历的条件
for(int i=0;i<words.size();i++){
maxl=maxl>=words[i].size()?maxl:words[i].size();
}
for(int i=0;i<board.size();i++){
for(int j=0;j<board[0].size();j++){
temp.push_back(board[i][j]);
dfs(board,ans,mark,temp,1,i,j);
temp.pop_back();
}
}
return ans;
}
void dfs(vector<vector<char>>& board, vector<string> &ans,vector<vector<int>> mark,string temp,int dp,int i,int j){
if(dp>maxl){
return;
}
if(mark[i][j]==1){
return;
}
//满足条件,放入答案
if(setTemp.count(temp)!=0 && find(ans.begin(),ans.end(),temp)==ans.end()){
ans.push_back(temp);
}
//标记为1 防止重复遍历
mark[i][j]=1;
for(int k=0;k<pos.size();k++){
int newX=i+pos[k][0];
int newY=j+pos[k][1];
if(newX >=0 && newX<board.size() && newY>=0 && newY<board[0].size()){
temp.push_back(board[newX][newY]);
dfs(board,ans,mark,temp,dp+1,newX,newY);
temp.pop_back();
}
}
mark[i][j]=0;
}
*/
//思路二:回溯法 + 字典树判断前缀优化
//先构造字典树结构
struct TrieNode {
string word;
unordered_map<char,TrieNode *> children;
TrieNode() {
this->word = "";
}
};
void insertTrie(TrieNode* root,const string &word) {
TrieNode*node=root;
//将字符串数组的字符串导入到字典树中
//有相同前缀的字符串会指向相同的map位置,但是word不同
for (auto c:word){
if (!node->children.count(c)) {
node->children[c]=new TrieNode();
}
node=node->children[c];
}
node->word=word;
}
//用于遍历
int dirs[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
//深度优先遍历
bool dfs(vector<vector<char>>& board, int x, int y, TrieNode * root, set<string>& res) {
char ch=board[x][y];
//如果目前的字符串不是words中的任何一个字符串的前缀,直接return
if (!root->children.count(ch)) {
return false;
}
//如果是前缀,则继续深度遍历
root=root->children[ch];
//如果root->word.size()>0,则代表遍历到此处有满足条件的字符串,所以放入答案
if (root->word.size()>0) {
res.insert(root->word);
}
//回溯法
board[x][y]='#';
for (int i=0;i<4;++i) {
int nx=x+dirs[i][0];
int ny=y+dirs[i][1];
if (nx>=0 && nx<board.size() && ny>=0 && ny<board[0].size()) {
if (board[nx][ny]!='#') {
dfs(board,nx,ny,root,res);
}
}
}
board[x][y] = ch;
return true;
}
vector<string> findWords(vector<vector<char>> & board, vector<string> & words) {
TrieNode * root = new TrieNode();
set<string> res;
vector<string> ans;
//导入字典树
for (auto & word: words){
insertTrie(root,word);
}
for (int i = 0; i<board.size(); ++i) {
for (int j=0;j<board[0].size(); ++j) {
dfs(board,i,j,root,res);
}
}
for (auto & word: res) {
ans.emplace_back(word);
}
return ans;
}
};