欢迎访问LeetCode题解合集
题目描述
给定一个 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 * 10^4
-
1 <= words[i].length <= 10
-
words[i]
由小写英文字母组成 -
words
中的所有字符串互不相同
题解:
法一:
字符串哈希 + 深搜。
记录 words
中每个单词的哈希值,并且记录最大长度。
然后枚举 board
中的每个位置,从该位置开始搜索,搜索的字符串长度不超过上面记录的最大值。
时间复杂度:$O(n * m * 3^L * L)$
额外空间复杂度:$O(n * m)$
class Solution {
using ull = unsigned long long;
const static int P = 131;
unordered_set<ull> hash;
unordered_set<ull> record;
int max_len{0};
vector<string> ret;
vector<vector<bool>> vis;
string ans;
vector<int> dx, dy;
int n, m;
public:
void dfs( int x, int y, ull now, int len, const vector<vector<char>>& board ) {
if ( hash.find(now) != hash.end() ) {
if ( record.find(now) == record.end() ) {
ret.emplace_back( ans );
record.insert( now );
}
}
if ( len >= max_len ) return;
vis[x][y] = true;
for ( int k = 0; k < 4; ++k ) {
int tx = x + dx[k];
int ty = y + dy[k];
if ( tx < 0 || tx >= n || ty < 0 || ty >= m || vis[tx][ty] )
continue;
ans.push_back( board[tx][ty] );
vis[tx][ty] = true;
dfs( tx, ty, now * P + board[tx][ty], len + 1, board );
vis[tx][ty] = false;
ans.pop_back();
}
vis[x][y] = false;
}
vector<string> findWords(vector<vector<char>>& board, vector<string>& words) {
if ( !board.size() || !board[0].size() ) return {};
for ( const string& word : words ) {
ull ans = 0;
for ( const char& it : word )
ans = ans * P + it;
hash.insert( ans );
if( word.size() > max_len )
max_len = word.size();
}
n = board.size(), m = board[0].size();
dx = vector<int>{-1, 0, 1, 0};
dy = vector<int>{0, 1, 0, -1};
vis = vector<vector<bool>>(n, vector<bool>(m));
for ( int i = 0; i < n; ++i ) {
for ( int j = 0; j < m; ++j ) {
ans = board[i][j];
dfs( i, j, board[i][j], 1, board );
}
}
return ret;
}
};
/*
时间:448ms,击败:29.78%
内存:8.3MB,击败:84.94%
*/
当然也可以遍历 words
中的每个单词,判断 board
中是否存在 word
,参考 单词搜索 。
class Solution {
private:
vector<string> ret;
vector<int> dx;
vector<int> dy;
vector<vector<bool>> vis;
int n, m;
public:
bool dfs( int x, int y, int p, const string& word, vector<vector<char>>& board ) {
if ( p >= word.size() ) return true;
vis[x][y] = true;
for ( int k = 0; k < 4; ++k ) {
int tx = x + dx[k];
int ty = y + dy[k];
if ( tx < 0 || tx >= n || ty < 0 || ty >= m || vis[tx][ty] )
continue;
if ( board[tx][ty] != word[p] ) continue;
if ( dfs( tx, ty, p + 1, word, board ) ) return true;
}
vis[x][y] = false;
return false;
}
vector<string> findWords(vector<vector<char>>& board, vector<string>& words) {
if ( !board.size() || !board[0].size() ) return {};
n = board.size(), m = board[0].size();
dx = vector<int>{-1, 0, 1, 0};
dy = vector<int>{0, 1, 0, -1};
bool flag;
for ( const string& word : words ) {
vis = vector<vector<bool>>(n, vector<bool>(m, false));
flag = false;
for ( int i = 0; i < n; ++i ) {
for ( int j = 0; j < m; ++j ) {
if ( board[i][j] != word[0] )
continue;
flag = dfs( i, j, 1, word, board );
if( flag ) {
ret.emplace_back( move(word) );
break;
}
}
if ( flag ) break;
}
}
return ret;
}
};
时间:8ms,击败:70.29%
内存:8.5MB,击败:55.77%
*/
速度出奇的快。。。
法二:
在 法一 中,我们是挨个判断相邻字符,在长度达到最大值时返回(剪枝),而如果我们只考虑与相邻字符构成的字符串在 words
中单词的前缀中,这样在搜索中就可以避免很多不必要的过程。
而判断前缀,就要用 trie树
了。
在 trie树
中,每个单词结束位置放置一个它在 words
中的下标(初值为 -1
),在搜索过程中,如果遇到下标不为 -1
的节点,说明找到一个单词,直接保存结果,并将节点的下标置为 -1
,防止结果重复。
时间复杂度:$O(n * m * 3^L)$
额外空间复杂度:$O(s)$ ,s
指 trie树
中字母总数。
class TrieNode {
public:
TrieNode *son[26]{nullptr};
int id{-1};
public:
TrieNode() { }
void insert( const string& word, int id ) {
auto root = this;
int u;
for ( const char& ch : word ) {
u = ch - 'a';
if( !root->son[u] )
root->son[u] = new TrieNode();
root = root->son[u];
}
root->id = id;
}
};
class Solution {
private:
TrieNode *root{nullptr};
vector<vector<bool>> vis;
vector<int> dx;
vector<int> dy;
vector<string> ret;
int n, m;
public:
vector<string> findWords(vector<vector<char>>& board, vector<string>& words) {
if ( !board.size() || !board[0].size() ) return {};
root = new TrieNode();
for( int i = 0; i < words.size(); ++i )
root->insert( words[i], i );
n = board.size(), m = board[0].size();
vis = vector<vector<bool>>( n, vector<bool>(m) );
dx = vector<int>{-1, 0, 1, 0};
dy = vector<int>{0, 1, 0, -1};
int u;
for ( int i = 0; i < n; ++i ) {
for ( int j = 0; j < m; ++j ) {
u = board[i][j] - 'a';
if ( !root->son[u] )
continue;
dfs( root->son[u], i, j, board, words );
}
}
return ret;
}
void dfs( TrieNode* root, int x, int y, vector<vector<char>>& board, const vector<string>& words ) {
if ( root->id != -1 ) {
ret.emplace_back( move(words[root->id]) );
root->id = -1;
}
vis[x][y] = true;
int u;
for ( int k = 0; k < 4; ++k ) {
int tx = x + dx[k];
int ty = y + dy[k];
if ( tx < 0 || tx >= n || ty < 0 || ty >= m || vis[tx][ty] )
continue;
u = board[tx][ty] - 'a';
if ( !root->son[u] ) continue;
dfs( root->son[u], tx, ty, board, words );
}
vis[x][y] = false;
}
};
/*
时间:348ms,击败:39.13%
内存:8.5MB,击败:63.62%
*/
优化:对于每个叶节点,其肯定是一个单词的结束节点,如果第一次遍历到一个叶节点,可以删除,避免重复遍历。
class TrieNode {
public:
TrieNode *son[26]{nullptr};
int id{-1};
int sons{0};
public:
TrieNode() { }
void insert( const string& word, int id ) {
auto root = this;
for ( const char& ch : word ) {
int u = ch - 'a';
if( !root->son[u] ) {
root->son[u] = new TrieNode();
++root->sons;
}
root = root->son[u];
}
root->id = id;
}
};
class Solution {
private:
TrieNode *root{nullptr};
vector<int> dx;
vector<int> dy;
vector<string> ret;
int n, m;
public:
vector<string> findWords(vector<vector<char>>& board, vector<string>& words) {
if ( !board.size() || !board[0].size() ) return {};
root = new TrieNode();
for( int i = 0; i < words.size(); ++i )
root->insert( words[i], i );
n = board.size(), m = board[0].size();
dx = vector<int>{-1, 0, 1, 0};
dy = vector<int>{0, 1, 0, -1};
for ( int i = 0; i < n; ++i ) {
for ( int j = 0; j < m; ++j ) {
int u = board[i][j] - 'a';
if ( !root->son[u] ) continue;
dfs( root, i, j, board, words );
}
}
return ret;
}
void dfs( TrieNode* root, int x, int y, vector<vector<char>>& board, const vector<string>& words ) {
int u = board[x][y] - 'a';
auto new_node = root->son[u];
if ( new_node->id != -1 ) {
ret.emplace_back( move(words[new_node->id]) );
new_node->id = -1;
}
char tmp = board[x][y];
board[x][y] = '#';
for ( int k = 0; k < 4; ++k ) {
int tx = x + dx[k];
int ty = y + dy[k];
if ( tx < 0 || tx >= n || ty < 0 || ty >= m || board[tx][ty] == '#' )
continue;
u = board[tx][ty] - 'a';
if ( !new_node->son[u] ) continue;
dfs( new_node, tx, ty, board, words );
}
board[x][y] = tmp;
if ( !new_node->sons ) {
--root->sons;
delete new_node;
u = board[x][y] - 'a';
root->son[u] = nullptr;
}
}
};
/*
时间:0ms,击败:100.00%
内存:8.5MB,击败:63.62%
*/