三、算法技巧
3.1 暴力搜索
3.1.1 回溯算法解题套路框架
框架
result = []
def backtrack(路径, 选择列表):
if 满足结束条件:
result.add(路径)
return
for 选择 in 选择列表:
做选择
backtrack(路径, 选择列表)
撤销选择
1、全排列 leetcode
题解:
时间复杂度:O(n x n!)
空间复杂度:O(n)
class Solution {
public:
vector<vector<int> > ans;
vector<int> vis;
void backtrace(vector<int>& nums, vector<int> path)
{
if(path.size()==nums.size())
{
ans.push_back(path);
return;
}
for(int i=0;i<nums.size();i++)
{
if(vis[i]==0)
{
vis[i]=1;
path.push_back(nums[i]);
backtrace(nums, path);
path.pop_back();
vis[i]=0;
}
}
}
vector<vector<int>> permute(vector<int>& nums) {
vis = vector<int>(nums.size(), 0);
backtrace(nums, {});
return ans;
}
};
2、N 皇后 leetcode
题解:
时间复杂度:O(n!)
空间复杂度:O(n)
class Solution {
public:
vector<vector<string> > ans;
bool judge(vector<string>& grid, int i, int j, int n)
{
for(int q=0;q<n;q++)
{
if(grid[i][q] == 'Q')
{
return false;
}
}
for(int p=0;p<n;p++)
{
if(grid[p][j] == 'Q')
{
return false;
}
}
int new_i = i;
int new_j = j;
while(new_i>=0 && new_j>=0)
{
if(grid[new_i][new_j]=='Q')
{
return false;
}
new_i--;
new_j--;
}
new_i = i;
new_j = j;
while(new_i<n && new_j<n)
{
if(grid[new_i][new_j]=='Q')
{
return false;
}
new_i++;
new_j++;
}
new_i = i;
new_j = j;
while(new_i>=0 && new_j<n)
{
if(grid[new_i][new_j]=='Q')
{
return false;
}
new_i--;
new_j++;
}
new_i = i;
new_j = j;
while(new_i<n && new_j>=0)
{
if(grid[new_i][new_j]=='Q')
{
return false;
}
new_i++;
new_j--;
}
return true;
}
void backtrace(vector<string>& grid, int index, int n)
{
if(index==n)
{
ans.push_back(grid);
return;
}
for(int i=0;i<n;i++)
{
if(judge(grid, index, i, n))
{
grid[index][i] = 'Q';
backtrace(grid, index+1, n);
grid[index][i] = '.';
}
}
}
vector<vector<string>> solveNQueens(int n) {
vector<string> grid(n);
string kong = "";
for(int i=0;i<n;i++)
{
kong+='.';
}
for(int i=0;i<n;i++)
{
grid[i]=kong;
}
backtrace(grid, 0, n);
return ans;
}
};
3.1.2 集合划分问题
1、划分为k个相等的子集 leetcode
题解:
时间复杂度:O(k^n)
空间复杂度:O(k)
class Solution {
public:
bool canPartitionKSubsets(vector<int>& nums, int k) {
if(k > nums.size()) return false;
int sum = accumulate(nums.begin(), nums.end(), 0);
if(sum % k != 0) return false;
// 记录每个子集中数字之和
bucket.resize(k);
// 每个桶中的数字之和应该为target
int target = sum/k;
// 从大到小放数字
sort(nums.rbegin(), nums.rend());
// index = 0, 表示从0号元素开始遍历
return backtrack(nums, 0, target);
}
private:
// 记录每个子集中数字之和
vector<int> bucket;
bool backtrack(vector<int> &nums, int index, int target){
// 如果所有数字遍历完了,是不需要检查bucket中的元素和是否都是target的。因为前面的 if(sum % k != 0) return false; 已经能保证只要所有元素都放入bucket中,那么bucket中的元素和都为target。
if(index == nums.size()){
return true;
}
// 注意:i 表示第i个子集,index 表示第index个数字
for(int i = 0; i < bucket.size(); i++){
// 如果这个数字放入子集i中使子集i中元素和超出target了
if(bucket[i] + nums[index] > target){
continue;
}
// 如果 当前子集的元素和 与 前一个子集的元素和 是一样的,那就跳过
if(i > 0 && bucket[i] == bucket[i-1]){
continue;
}
// 将数字放入子集i中
bucket[i] += nums[index];
// 递归穷举下一个数字的情况
if(backtrack(nums, index + 1, target)){
return true;
}
// 撤销选择
bucket[i] -= nums[index];
}
// 如果 nums[index] 放入哪个子集都不行
return false;
}
};
3.1.3 排列/组合/子集问题
1、子集 leetcode
(元素无重不可复选)
题解:
时间复杂度:O(n*2^n)
空间复杂度:O(n)
class Solution {
public:
vector<vector<int> > ans;
void backtrace(vector<int>& nums, int idx, vector<int> path)
{
ans.push_back(path);
for(int i=idx;i<nums.size();i++)
{
path.push_back(nums[i]);
backtrace(nums, i+1, path);
path.pop_back();
}
}
vector<vector<int>> subsets(vector<int>& nums) {
backtrace(nums, 0, {});
return ans;
}
};
2、组合 leetcode
(元素无重不可复选)
题解:转化为子集问题
时间复杂度:O(
C
n
k
C_{n}^k
Cnk×k)
空间复杂度:O(n)
class Solution {
public:
vector<vector<int> > ans;
vector<int> nums;
void backtrace(int idx, int k, vector<int> path)
{
if(path.size()==k)
{
ans.push_back(path);
return;
}
for(int i=idx;i<nums.size();i++)
{
path.push_back(nums[i]);
backtrace(i+1, k, path);
path.pop_back();
}
}
vector<vector<int>> combine(int n, int k) {
for(int i=1;i<=n;i++)
{
nums.push_back(i);
}
backtrace(0, k, {});
return ans;
}
};
题解:提前剪枝
时间复杂度:O(
C
n
k
C_{n}^k
Cnk×k)
空间复杂度:O(n)
class Solution {
public:
vector<vector<int> > ans;
vector<int> nums;
void backtrace(int cur, int k, vector<int> path, int n)
{
if(path.size() + (n - cur) < k)
{
return;
}
if(path.size()==k)
{
ans.push_back(path);
return;
}
path.push_back(nums[cur]);
backtrace(cur+1, k, path, n);
path.pop_back();
backtrace(cur+1, k, path, n);
}
vector<vector<int>> combine(int n, int k) {
for(int i=1;i<=n;i++)
{
nums.push_back(i);
}
backtrace(0, k, {}, n);
return ans;
}
};
3、全排列 leetcode
(元素无重不可复选)
题解:
时间复杂度:O(n x n!)
空间复杂度:O(n)
class Solution {
public:
vector<vector<int> > ans;
vector<int> vis;
void backtrace(vector<int>& nums, vector<int> path)
{
if(path.size()==nums.size())
{
ans.push_back(path);
return;
}
for(int i=0;i<nums.size();i++)
{
if(vis[i]==0)
{
vis[i]=1;
path.push_back(nums[i]);
backtrace(nums, path);
path.pop_back();
vis[i]=0;
}
}
}
vector<vector<int>> permute(vector<int>& nums) {
int n = nums.size();
vis.resize(n, 0);
backtrace(nums, {});
return ans;
}
};
4、子集 II leetcode
(元素可重不可复选)
题解:
时间复杂度:O(n x 2^n)
空间复杂度:O(n)
class Solution {
public:
vector<vector<int> > ans;
void backtrace(vector<int>& nums, int idx, vector<int> path)
{
ans.push_back(path);
for(int i=idx;i<nums.size();i++)
{
if(i>idx && nums[i]==nums[i-1])
continue;
path.push_back(nums[i]);
backtrace(nums, i+1, path);
path.pop_back();
}
}
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
sort(nums.begin(), nums.end());
backtrace(nums, 0, {});
return ans;
}
};
5、组合总和 II leetcode
题解:
时间复杂度:O(n x 2^n)
空间复杂度:O(n)
class Solution {
public:
vector<vector<int> > ans;
void backtrace(vector<int>& candidates, int idx, int sum, int target, vector<int> path)
{
if(sum > target)
{
return;
}
if(sum==target)
{
ans.push_back(path);
}
for(int i=idx;i<candidates.size();i++)
{
if(i>idx && candidates[i]==candidates[i-1])
continue;
path.push_back(candidates[i]);
backtrace(candidates, i+1, sum+candidates[i], target, path);
path.pop_back();
}
}
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
sort(candidates.begin(), candidates.end());
backtrace(candidates, 0, 0, target, {});
return ans;
}
};
6、全排列 II leetcode
题解:
时间复杂度:O(n x n!)
空间复杂度:O(n)
class Solution {
public:
vector<vector<int>> ans;
vector<int> vis;
void backtrace(vector<int>& nums, vector<int> path)
{
if(path.size()==nums.size())
{
ans.push_back(path);
return;
}
for(int i=0;i<nums.size();i++)
{
if(vis[i]==0)
{
// 新添加的剪枝逻辑,固定相同的元素在排列中的相对位置
if(i>0 && nums[i]==nums[i-1] && vis[i-1]==0)
// 如果前面的相邻相等元素没有用过,那么当前元素也不能用,要跳过。只有当前面相等的元素用过了,当前相等元素才能用,这样保持了相对顺序。
continue;
vis[i]=1;
path.push_back(nums[i]);
backtrace(nums, path);
path.pop_back();
vis[i]=0;
}
}
}
vector<vector<int>> permuteUnique(vector<int>& nums) {
sort(nums.begin(), nums.end());
vis.resize(nums.size(), 0);
backtrace(nums, {});
return ans;
}
};
7、组合总和 leetcode
(元素无重可复选)
题解:
class Solution {
public:
vector<vector<int> > ans;
void backtrace(vector<int>& candidates, int idx, int sum, int target, vector<int> path)
{
if(sum>target)
{
return;
}
if(sum==target)
{
ans.push_back(path);
return;
}
for(int i=idx;i<candidates.size();i++)
{
path.push_back(candidates[i]);
backtrace(candidates, i, sum+candidates[i], target, path);
path.pop_back();
}
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
backtrace(candidates, 0, 0, target, {});
return ans;
}
};
8、总结
形式一、元素无重不可复选,即 nums 中的元素都是唯一的,每个元素最多只能被使用一次
/* 组合/子集问题回溯算法框架 */
void backtrack(int[] nums, int start) {
// 回溯算法标准框架
for (int i = start; i < nums.length; i++) {
// 做选择
track.addLast(nums[i]);
// 注意参数
backtrack(nums, i + 1);
// 撤销选择
track.removeLast();
}
}
/* 排列问题回溯算法框架 */
void backtrack(int[] nums) {
for (int i = 0; i < nums.length; i++) {
// 剪枝逻辑
if (used[i]) {
continue;
}
// 做选择
used[i] = true;
track.addLast(nums[i]);
backtrack(nums);
// 撤销选择
track.removeLast();
used[i] = false;
}
}
形式二、元素可重不可复选,即 nums 中的元素可以存在重复,每个元素最多只能被使用一次,其关键在于排序和剪枝
Arrays.sort(nums);
/* 组合/子集问题回溯算法框架 */
void backtrack(int[] nums, int start) {
// 回溯算法标准框架
for (int i = start; i < nums.length; i++) {
// 剪枝逻辑,跳过值相同的相邻树枝
if (i > start && nums[i] == nums[i - 1]) {
continue;
}
// 做选择
track.addLast(nums[i]);
// 注意参数
backtrack(nums, i + 1);
// 撤销选择
track.removeLast();
}
}
Arrays.sort(nums);
/* 排列问题回溯算法框架 */
void backtrack(int[] nums) {
for (int i = 0; i < nums.length; i++) {
// 剪枝逻辑
if (used[i]) {
continue;
}
// 剪枝逻辑,固定相同的元素在排列中的相对位置
if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) {
continue;
}
// 做选择
used[i] = true;
track.addLast(nums[i]);
backtrack(nums);
// 撤销选择
track.removeLast();
used[i] = false;
}
}
形式三、元素无重可复选,即 nums 中的元素都是唯一的,每个元素可以被使用若干次,只要删掉去重逻辑即可
/* 组合/子集问题回溯算法框架 */
void backtrack(int[] nums, int start) {
// 回溯算法标准框架
for (int i = start; i < nums.length; i++) {
// 做选择
track.addLast(nums[i]);
// 注意参数
backtrack(nums, i); // 这里和形式一不同,不需要i+1
// 撤销选择
track.removeLast();
}
}
/* 排列问题回溯算法框架 */
void backtrack(int[] nums) {
for (int i = 0; i < nums.length; i++) {
// 做选择
track.addLast(nums[i]);
backtrack(nums);
// 撤销选择
track.removeLast();
}
}
3.1.4 岛屿问题
DFS框架
// 二叉树遍历框架
void traverse(TreeNode root) {
traverse(root.left);
traverse(root.right);
}
// 二维矩阵遍历框架
void dfs(int[][] grid, int i, int j, boolean[][] visited) {
int m = grid.length, n = grid[0].length;
if (i < 0 || j < 0 || i >= m || j >= n) {
// 超出索引边界
return;
}
if (visited[i][j]) {
// 已遍历过 (i, j)
return;
}
// 进入节点 (i, j)
visited[i][j] = true;
dfs(grid, i - 1, j, visited); // 上
dfs(grid, i + 1, j, visited); // 下
dfs(grid, i, j - 1, visited); // 左
dfs(grid, i, j + 1, visited); // 右
}
题解:DFS
时间复杂度:O(mn)
空间复杂度:O(mn)
class Solution {
public:
vector<vector<int> > vis;
int ans=0;
int X[4] = {-1, 1, 0, 0};
int Y[4] = {0, 0, -1, 1};
void dfs(vector<vector<char>>& grid, int x, int y)
{
int m = grid.size();
int n = grid[0].size();
for(int i=0;i<4;i++)
{
int newX = x+X[i];
int newY = y+Y[i];
if(newX>=0 &&newX<m && newY>=0 && newY<n)
{
if(vis[newX][newY]==0 && grid[newX][newY]=='1')
{
vis[newX][newY]=1;
dfs(grid, newX, newY);
}
}
}
}
int numIslands(vector<vector<char>>& grid) {
int m = grid.size();
int n = grid[0].size();
vis = vector<vector<int> > (m, vector<int>(n, 0));
for(int i=0;i<m;i++)
{
for(int j=0;j<n;j++)
{
if(grid[i][j]=='1' && vis[i][j]==0)
{
ans++;
dfs(grid, i, j);
}
}
}
return ans;
}
};
题解:BFS
时间复杂度:O(mn)
空间复杂度:O(min(m,n))
class Solution {
public:
int numIslands(vector<vector<char>>& grid) {
int nr = grid.size();
if (!nr) return 0;
int nc = grid[0].size();
int num_islands = 0;
for (int r = 0; r < nr; ++r) {
for (int c = 0; c < nc; ++c) {
if (grid[r][c] == '1') {
++num_islands;
grid[r][c] = '0';
queue<pair<int, int>> neighbors;
neighbors.push({r, c});
while (!neighbors.empty()) {
auto rc = neighbors.front();
neighbors.pop();
int row = rc.first, col = rc.second;
if (row - 1 >= 0 && grid[row-1][col] == '1') {
neighbors.push({row-1, col});
grid[row-1][col] = '0';
}
if (row + 1 < nr && grid[row+1][col] == '1') {
neighbors.push({row+1, col});
grid[row+1][col] = '0';
}
if (col - 1 >= 0 && grid[row][col-1] == '1') {
neighbors.push({row, col-1});
grid[row][col-1] = '0';
}
if (col + 1 < nc && grid[row][col+1] == '1') {
neighbors.push({row, col+1});
grid[row][col+1] = '0';
}
}
}
}
}
return num_islands;
}
};
题解:
时间复杂度:O(mn)
空间复杂度:O(mn)
class Solution {
public:
int X[4] = {-1, 1, 0, 0};
int Y[4] = {0, 0, -1, 1};
int ans=0;
void dfs(vector<vector<int>>& grid, int x, int y)
{
int m = grid.size();
int n = grid[0].size();
for(int i=0;i<4;i++)
{
int newX = x+X[i];
int newY = y+Y[i];
if(newX>=0&&newX<m && newY>=0&&newY<n)
{
if(grid[newX][newY]==0)
{
grid[newX][newY]=1;
dfs(grid, newX, newY);
}
}
}
}
int closedisland(vector<vector<int>>& grid) {
int m = grid.size();
int n = grid[0].size();
for(int i=0;i<m;i++)
{
if(grid[i][0]==0)
{
grid[i][0]=1;
dfs(grid, i, 0);
}
if(grid[i][n-1]==0)
{
grid[i][n-1]=1;
dfs(grid, i, n-1);
}
}
for(int j=0;j<n;j++)
{
if(grid[0][j]==0)
{
grid[0][j]=1;
dfs(grid, 0, j);
}
if(grid[m-1][j]==0)
{
grid[m-1][j]=1;
dfs(grid, m-1, j);
}
}
for(int i=0;i<m;i++)
{
for(int j=0;j<n;j++)
{
if(grid[i][j]==0)
{
ans++;
grid[i][j]=1;
dfs(grid, i, j);
}
}
}
return ans;
}
};
题解:
时间复杂度:O(mn)
空间复杂度:O(mn)
class Solution {
public:
int X[4] = {-1, 1, 0, 0};
int Y[4] = {0, 0, -1, 1};
void dfs(vector<vector<int> >& grid, int x, int y)
{
int m = grid.size();
int n = grid[0].size();
grid[x][y]=0;
for(int i=0;i<4;i++)
{
int newX = x+X[i];
int newY = y+Y[i];
if(newX>=0&&newX<m &&newY>=0&&newY<n && grid[newX][newY]==1)
{
dfs(grid, newX, newY);
}
}
}
int numEnclaves(vector<vector<int>>& grid) {
int m = grid.size();
int n = grid[0].size();
for(int i=0;i<m;i++)
{
if(grid[i][0]==1)
{
dfs(grid, i, 0);
}
if(grid[i][n-1]==1)
{
dfs(grid, i, n-1);
}
}
for(int j=0;j<n;j++)
{
if(grid[0][j]==1)
{
dfs(grid, 0, j);
}
if(grid[m-1][j]==1)
{
dfs(grid, m-1, j);
}
}
int ans=0;
for(int i=0;i<m;i++)
{
for(int j=0;j<n;j++)
{
if(grid[i][j]==1)
{
ans++;
}
}
}
return ans;
}
};
4、岛屿的最大面积 leetcode
题解:
时间复杂度:O(mn)
空间复杂度:O(mn)
class Solution {
public:
int X[4] = {-1, 1, 0, 0};
int Y[4] = {0, 0, -1, 1};
int area=0;
void dfs(vector<vector<int> >& grid, int x, int y)
{
int m = grid.size();
int n = grid[0].size();
grid[x][y]=0;
for(int i=0;i<4;i++)
{
int newX = x+X[i];
int newY = y+Y[i];
if(newX>=0&&newX<m &&newY>=0&&newY<n && grid[newX][newY]==1)
{
area++;
dfs(grid, newX, newY);
}
}
}
int maxAreaOfIsland(vector<vector<int>>& grid) {
int m = grid.size();
int n = grid[0].size();
int ans=0;
for(int i=0;i<m;i++)
{
for(int j=0;j<n;j++)
{
area=0;
if(grid[i][j]==1)
{
area++;
dfs(grid, i, j);
}
ans = max(ans, area);
}
}
return ans;
}
};
题解:
时间复杂度:O(mn)
空间复杂度:O(mn)
class Solution {
public:
int X[4] = {-1, 1, 0, 0};
int Y[4] = {0, 0, -1, 1};
void dfs(vector<vector<int>>& grid1, vector<vector<int>>& grid2, int x, int y, int& f_mark)
{
int m = grid2.size();
int n = grid2[0].size();
if(grid2[x][y]==1 && grid1[x][y]==1)
{
grid2[x][y]=0;
}else{
f_mark=0;
return;
}
for(int i=0;i<4;i++)
{
int newX = x+X[i];
int newY = y+Y[i];
if(newX>=0&&newX<m&&newY>=0&&newY<n&&grid2[newX][newY]==1)
{
dfs(grid1, grid2, newX, newY, f_mark);
}
}
}
int countSubIslands(vector<vector<int>>& grid1, vector<vector<int>>& grid2) {
int m = grid1.size();
int n = grid1[0].size();
int ans=0;
for(int i=0;i<m;i++)
{
for(int j=0;j<n;j++)
{
if(grid2[i][j]==1)
{
int f_mark=1;
dfs(grid1, grid2, i, j, f_mark);
if(f_mark)
{
ans++;
}
}
}
}
return ans;
}
};
6、不同岛屿的个数 lintcode
题解:
时间复杂度:O(mn)
空间复杂度:O(mn)
比方说「下,右,撤销右,撤销下」和「下,撤销下,右,撤销右」显然是两个不同的遍历顺序,但如果不记录撤销操作,那么它俩都是「下,右」,成了相同的遍历顺序,显然是不对的。
class Solution {
public:
/**
* @param grid: a list of lists of integers
* @return: return an integer, denote the number of distinct islands
*/
int X[4] = {-1, 1, 0, 0};
int Y[4] = {0, 0, -1, 1};
void dfs(vector<vector<int>> &grid, int x, int y, string& path)
{
int m = grid.size();
int n = grid[0].size();
for(int i=0;i<4;i++)
{
int newX = x+X[i];
int newY = y+Y[i];
if(newX>=0&&newX<m&&newY>=0&&newY<n&&grid[newX][newY]==1)
{
path+=i+'0'; // 进入
path+=',';
grid[newX][newY]=0;
dfs(grid, newX, newY, path);
path+=-i+'0'; // 离开
path+=',';
}
}
}
int numberofdistinctIslands(vector<vector<int>> &grid) {
// write your code here
int m = grid.size();
int n = grid[0].size();
unordered_set<string> hashset;
for(int i=0;i<m;i++)
{
for(int j=0;j<n;j++)
{
if(grid[i][j]==1)
{
string path="";
grid[i][j]=0;
dfs(grid, i, j, path);
hashset.insert(path);
}
}
}
return hashset.size();
}
};
3.1.5 解数独
题解:
时间复杂度:O(mn)
空间复杂度:O(mn)
class Solution {
public:
int row[9][10]={0};
int col[9][10]={0};
int grid[3][3][10]={0};
vector<pair<int, int> > sequence;
bool flag = false;
void dfs(vector<vector<char>>& board, int cur)
{
if(cur==sequence.size())
{
flag=true;
return;
}
int x = sequence[cur].first;
int y = sequence[cur].second;
for(int num=1;num<=9 && flag==false;num++)
{
if(row[x][num]==0 && col[y][num]==0 && grid[x/3][y/3][num]==0)
{
board[x][y] = num+'0';
row[x][num]=1;
col[y][num]=1;
grid[x/3][y/3][num]=1;
dfs(board, cur+1);
// board[x][y] = '.'; 这里回退不用把'.'填回去,这会把最后一次填的东西又盖住。因为如果填错数字,下一次可以直接覆盖的,主要有标记数组就行。
row[x][num]=0;
col[y][num]=0;
grid[x/3][y/3][num]=0;
}
}
}
void solveSudoku(vector<vector<char>>& board) {
for(int i=0;i<9;i++)
{
for(int j=0;j<9;j++)
{
if(board[i][j]=='.')
{
sequence.push_back(make_pair(i, j));
}else{
int num = board[i][j]-'0';
row[i][num]=1;
col[j][num]=1;
grid[i/3][j/3][num]=1;
}
}
}
dfs(board, 0);
}
};
3.1.6 括号生成
题解:
时间复杂度:O(
4
n
(
n
)
\frac{4^n}{\sqrt(n)}
(n)4n)
空间复杂度:O(n)
class Solution {
public:
vector<string> ans;
void backtrace(int left, int right, int n, string zuhe)
{
if(zuhe.size()==n*2)
{
ans.push_back(zuhe);
return;
}
if(left<n)
{
backtrace(left+1, right, n, zuhe+'(');
}
if(right<left)
{
backtrace(left, right+1, n, zuhe+')');
}
}
vector<string> generateParenthesis(int n) {
backtrace(0, 0, n, "");
return ans;
}
};
3.1.7 BFS 算法解题套路框架
题解:
时间复杂度:O(n)
空间复杂度:O(n)
class Solution {
public:
int minDepth(TreeNode* root) {
if(root==nullptr)
{
return 0;
}
queue<TreeNode*> q;
q.push(root);
int h = 1;
while(!q.empty())
{
int size = q.size();
while(size)
{
TreeNode* node = q.front();
q.pop();
size--;
if(node->left==nullptr&&node->right==nullptr)
{
return h;
}
if(node->left!=nullptr)
{
q.push(node->left);
}
if(node->right!=nullptr)
{
q.push(node->right);
}
}
h++;
}
return h;
}
};
2、打开转盘锁 leetcode
题解:
时间复杂度:O(
b
d
⋅
d
2
b^d\cdot d^2
bd⋅d2 + md),其中 b是数字的进制,d是转盘数字的位数,m是数组deadends 的长度
空间复杂度:O( b d ⋅ d b^d\cdot d bd⋅d + m)
class Solution {
public:
char num_prev(char x)
{
return (x == '0' ? '9' : x - 1);
}
char num_succ(char x)
{
return (x == '9' ? '0' : x + 1);
}
vector<string> get(string& status) // 枚举 status 通过一次旋转得到的数字
{
vector<string> ret;
for (int i = 0; i < 4; ++i) // 分别枚举每一位向前、向后能得到的数字
{
char num = status[i];
status[i] = num_prev(num);
ret.push_back(status);
status[i] = num_succ(num);
ret.push_back(status);
status[i] = num;
}
return ret;
}
int openLock(vector<string>& deadends, string target) {
if (target == "0000") {
return 0;
}
unordered_set<string> dead(deadends.begin(), deadends.end());
if (dead.count("0000")) {
return -1;
}
queue<pair<string, int>> q;
q.emplace("0000", 0);
unordered_set<string> seen = {"0000"};
while (!q.empty()) {
auto [status, step] = q.front();
q.pop();
vector<string> next_status = get(status); // 获取在当前数字状态下,可能获得的下一次转动数字结果
for (string str: next_status) {
if (!seen.count(str) && !dead.count(str)) {
if (str == target) {
return step + 1;
}
q.emplace(str, step + 1);
seen.insert(str);
}
}
}
return -1;
}
};
3、滑动谜题 leetcode
题解:
时间复杂度:O((mn)! * mn)
空间复杂度:O((mn)! * mn)
class Solution {
public:
bool judge(vector<vector<int>>& board)
{
if(board[0][0]==1&&board[0][1]==2&&board[0][2]==3&&board[1][0]==4&&board[1][1]==5&&board[1][2]==0)
{
return true;
}
return false;
}
int X[4] = {-1, 1, 0, 0};
int Y[4] = {0, 0, -1, 1};
int slidingPuzzle(vector<vector<int>>& board) {
unordered_map<string, int> vis;
queue<vector<vector<int> > > q;
q.push(board);
int step=0;
int ans=INT_MAX;
while(q.size()!=0)
{
int size=q.size();
while(size)
{
auto node = q.front();
q.pop();
if(judge(node))
{
ans = min(ans, step);
}
int x,y;
for(int i=0;i<2;i++)
{
for(int j=0;j<3;j++)
{
if(node[i][j]==0)
{
x=i;
y=j;
break;
}
}
}
for(int i=0;i<4;i++)
{
int newX = x+X[i];
int newY = y+Y[i];
if(newX>=0&&newX<2&&newY>=0&&newY<3)
{
swap(node[x][y], node[newX][newY]);
string str="";
for(int m=0;m<2;m++)
{
for(int n=0;n<3;n++)
{
str+=node[m][n]+'0';
}
}
if(vis.count(str)==0)
{
q.push(node);
vis[str]++;
}
swap(node[x][y], node[newX][newY]);
}
}
size--;
}
step++;
}
if(ans!=INT_MAX)
{
return ans;
}else{
return -1;
}
}
};
3.2 位运算
3.2.1 常用的位操作
几个有趣的位操作
- 利用异或操作 ^ 和空格进行英文字符大小写互换
('d' ^ ' ') = 'D'
('D' ^ ' ') = 'd'
- 判断两个数是否异号
int x = -1, y = 2;
boolean f = ((x ^ y) < 0); // true
int x = 3, y = 2;
boolean f = ((x ^ y) < 0); // false
- n & (n-1) 的运用:消除数字 n 的二进制表示中的最后一个 1
1、只出现一次的数字 leetcode
题解:
时间复杂度:O(n)
空间复杂度:O(1)
class Solution {
public:
int singleNumber(vector<int>& nums) {
int ans = nums[0];
for(int i=1;i<nums.size();i++)
{
ans ^= nums[i];
}
return ans;
}
};
2、丢失的数字 leetcode
题解:
时间复杂度:O(n)
空间复杂度:O(1)
class Solution {
public:
int missingNumber(vector<int>& nums) {
int sum = 0;
for(int num:nums)
{
sum+=num;
}
int n = nums.size();
return (n+1)*n/2 - sum;
}
};
题解:位运算
时间复杂度:O(n)
空间复杂度:O(1)
class Solution {
public:
int missingNumber(vector<int>& nums) {
int res=0;
int n = nums.size();
res ^= n;
for(int i=0;i<nums.size();i++)
{
res = res ^ i ^ nums[i];
}
return res;
}
};
3、位1的个数 leetcode
题解:
时间复杂度:O(n)
空间复杂度:O(1)
class Solution {
public:
int hammingWeight(uint32_t n) {
int ans=0;
while(n!=0)
{
n = n&(n-1);
ans++;
}
return ans;
}
};
4、2 的幂 leetcode
题解:
时间复杂度:O(1)
空间复杂度:O(1)
class Solution {
public:
bool isPowerOfTwo(int n) {
if(n==0 || n<0)
return false;
n = n&(n-1);
if(n==0)
{
return true;
}else{
return false;
}
}
};
3.2.2 阶乘
1、阶乘后的零 leetcode
题解:
时间复杂度:O(logn)
空间复杂度:O(1)
class Solution {
public:
int trailingZeroes(int n) {
int ans=0;
while(n>0)
{
ans+=n/5; // 每次把5的倍数中因子5的数量计算了,那么剩下就是更高层5的倍数中另外的5
n/=5;
}
return ans;
}
};
题解:用二分解方程
时间复杂度:O(
l
o
g
2
K
log^2 K
log2K)
空间复杂度:O(1)
class Solution {
public:
int f(long long n)
{
int num=0;
while(n>0)
{
num+=n/5;
n/=5;
}
return num;
}
int preimageSizefZF(int k) {
long long r = (long long)5 * k;
long long l = (long long)4 * k;
while(l<=r)
{
long long mid = (r+l)/2;
int num = f(mid);
if(num==k)
{
return 5;
}else if(num<k)
{
l=mid+1;
}else{
r=mid-1;
}
}
return 0;
}
};
3.2.3 素数
1、计数质数 leetcode
题解:
时间复杂度:O( nlog(logn) )
空间复杂度:O(n)
class Solution {
public:
int countPrimes(int n) {
if(n<2)
return 0;
vector<int> prime(n+1, 1);
prime[0]=0;
prime[1]=0;
int ans=0;
for(int i=2;i<n;i++)
{
if(prime[i]==1)
{
ans++;
// 后面i的倍数,全是合数,不是质数.
if((long long )i*i<n)
{
for(long long j=i*i;j<n;j+=i) // 从i*i开始遍历,2*i、3*i等都会被前面的数标记过
{
prime[j]=0;
}
}
}
}
return ans;
}
};
3.2.4 模幂运算
1、超级次方 leetcode
题解:
时间复杂度:O(
∑
i
=
1
m
l
o
g
(
b
i
)
\sum_{i=1}^{m} log(b_i)
∑i=1mlog(bi))
空间复杂度:O(1)
class Solution {
public:
long pow(int a, int n)
{
long res=1;
long x = (long) a;
while(n!=0)
{
if(n&1) // 表示二进制末尾有1,那就乘上一个x。如果是0的话,对应是乘1,没变化
{
res = res * x % 1337;
}
x = x*x % 1337; // 不断平方,这样就相当于在二进制上累积
n = n >> 1;
}
return res;
}
int superPow(int a, vector<int>& b) {
long ans = 1;
for(int i=b.size()-1;i>=0;i--)
{
ans = ans * pow(a, b[i]) % 1337;
a = pow(a, 10);
}
return ans;
}
};
3.2.5 错误的集合
题解:
时间复杂度:O(n)
空间复杂度:O(1)
class Solution {
public:
vector<int> findErrorNums(vector<int>& nums) {
int n = nums.size();
// 假设丢失的数字x,重复数字y
int xorm = 0;
for(int i=0;i<nums.size();i++)
{
xorm^=nums[i];
}
for(int i=1;i<=n;i++)
{
xorm^=i;
}
// 此时xorm,就是x^y。因为在这2n个数中,其他数字都出现2次,x出现1次,y出现3次
int lowbit = xorm & (-xorm); //lowbit 为 x 和 y 的二进制中的最低不同位
int num1=0, num2=0;
for(int i=0;i<nums.size();i++)
{
if(nums[i] & lowbit)
{
num1^=nums[i];
}else{
num2^=nums[i];
}
}
for(int i=1;i<=n;i++)
{
if(i & lowbit)
{
num1 ^= i;
}else{
num2 ^= i;
}
}
// 此时lowbit已经把2n个数,再次区分开。把x和y也区分开,但是不知道num1=x,还是num2=x。因此需要再次遍历数组
for(int i=0;i<nums.size();i++)
{
if(nums[i]==num1)
{
return {num1, num2};
}else if(nums[i]==num2)
{
return {num2, num1};
}
}
return {};
}
};
3.2.6 在无限序列中随机抽取元素
题解:
时间复杂度:O(n)
空间复杂度:O(1)
class Solution {
public:
ListNode* root;
Solution(ListNode* head) {
root = head;
}
int getRandom() {
ListNode* p = root;
int len=1;
int ans=p->val;
while(p!=nullptr)
{
int idx = rand()%len + 1; // 这里取值范围为[1,len]
if(idx==len) // 因此取到len的概率是 1/len,是等概率的
{
ans = p->val; // 这里不能提前break,因为要考虑整个链表
}
p=p->next;
len++;
}
return ans;
}
};
题解:水塘抽样
时间复杂度:O(n)
空间复杂度:O(1)
class Solution {
public:
vector<int> vec;
Solution(vector<int>& nums) {
vec = nums;
}
int pick(int target) {
int i=1, ans=0;
for(int k=0;k<vec.size();k++)
{
if(vec[k]==target)
{
if(rand()%i==0)
{
ans=k;
}
i++;
}
}
return ans;
}
};
3.3 经典面试题
3.3.1 分治算法
1、为运算表达式设计优先级 leetcode
题解:分治
时间复杂度:O(2^n)
空间复杂度:O(2^n)
class Solution {
public:
vector<int> diffWaysToCompute(string expression) {
vector<int> count;
for(int i=0;i<expression.size();i++)
{
char c = expression[i];
if(c=='+'||c=='-'||c=='*')
{
string left = expression.substr(0, i);
string right = expression.substr(i+1);
vector<int> l_res = diffWaysToCompute(left); // 分别计算左右表达式的值
vector<int> r_res = diffWaysToCompute(right);
for(auto l : l_res)
{
for(auto r: r_res)
{
if(c=='+')
{
count.push_back(l+r);
}else if(c=='-')
{
count.push_back(l-r);
}else{
count.push_back(l*r);
}
}
}
}
}
if(count.size()==0) // 说明此时只有一个数字,没有运算符
{
count.push_back(stoi(expression));
}
return count;
}
};
3.3.2 区间问题
题解:贪心
时间复杂度:O(nlogn)
空间复杂度:O(logn)
class Solution {
public:
int removeCoveredIntervals(vector<vector<int>>& intervals) {
sort(intervals.begin(), intervals.end(),
[](const vector<int>& a, const vector<int>& b)
{
if(a[0]!=b[0])
{
return a[0]<b[0]; // 保证了当前区间的左端点一定小于等于下一个区间
}else{
return a[1]>b[1];
}
}
);
int ans=1;
int right = intervals[0][1];
for(int i=1;i<intervals.size();i++)
{
if(intervals[i][1]>right) // 如果有新的右边界,那意味着新区间不能被覆盖
{
ans++;
right = intervals[i][1];
}
}
return ans;
}
};
2、合并区间 leetcode
题解:
时间复杂度:O(nlogn)
空间复杂度:O(logn)
class Solution {
public:
vector<vector<int>> merge(vector<vector<int>>& intervals) {
sort(intervals.begin(), intervals.end(),
[](const vector<int>& a, const vector<int>& b)
{
if(a[0]!=b[0])
{
return a[0]<b[0];
}else{
return a[1]>b[1];
}
}
);
vector<vector<int> > ans;
int left=intervals[0][0];
int right = intervals[0][1];
for(int i=1;i<intervals.size();i++)
{
if(intervals[i][0]>right) // 新起一个区间
{
ans.push_back({left, right});
left = intervals[i][0];
right = intervals[i][1];
}else{
if(intervals[i][1]>=right) // 有交集,更新右边界
{
right = intervals[i][1];
}
}
}
ans.push_back({left, right});
return ans;
}
};
3、区间列表的交集 leetcode
题解:
时间复杂度:O(min(n,m))
空间复杂度:O(1)
class Solution {
public:
vector<vector<int>> intervalIntersection(vector<vector<int>>& firstList, vector<vector<int>>& secondList) {
int i=0;
int j=0;
vector<vector<int> > ans;
if(firstList.size()==0||secondList.size()==0)
{
return {};
}
while(i<firstList.size() && j<secondList.size())
{
int start_i = firstList[i][0];
int end_i = firstList[i][1];
int start_j = secondList[j][0];
int end_j = secondList[j][1];
int start, end;
if(start_i<start_j)
{
start = start_j;
if(end_i<end_j)
{
end = end_i;
i++;
}else{
end = end_j;
j++;
}
}else{
start = start_i;
if(end_i<end_j)
{
end = end_i;
i++;
}else{
end = end_j;
j++;
}
}
if(start<=end)
ans.push_back({start, end});
}
return ans;
}
};
3.3.3 连续子序列的划分问题
1、分割数组为连续子序列 leetcode
题解:
时间复杂度:O(n)
空间复杂度:O(n)
class Solution {
public:
bool isPossible(vector<int>& nums) {
unordered_map<int, int> nc; // 记录每个数有多少个
unordered_map<int, int> tail; // 记录序列的最后一个数字是什么
for(auto v:nums)
{
nc[v]++;
}
for(auto num : nums)
{
if(nc[num]==0) // 这个数已经被消耗完
{
continue;
}else if(nc[num]>0 && tail[num-1]>0) // 当前数还有,且存在前一个数的尾巴,那就优先加入前一个序列中
{
nc[num]--;
tail[num-1]--;
tail[num]++;
}else if(nc[num]>0 && nc[num+1]>0 && nc[num+2]>0) // 否则连续取3个数形成一个队列
{
nc[num]--;
nc[num+1]--;
nc[num+2]--;
tail[num+2]++;
}else{
//只有检查到某个数时,这个数未被消耗完,且既不能和前面组成连续子序列,也不能和后面组成连续子序列时,无法分割
return false;
}
}
return true;
}
};
3.3.4 烧饼排序算法
1、煎饼排序 leetcode
题解:
时间复杂度:O(nlogn)
空间复杂度:O(n)
class Solution {
public:
vector<int> pancakeSort(vector<int>& arr) {
vector<int> ans;
vector<int> tmp = arr;
sort(tmp.begin(), tmp.end());
int last = tmp.size()-1;
while(last!=0)
{
if(arr[last]==tmp[last])
{
last--;
}else{
int idx = 0;
for(int i=0;i<arr.size();i++)
{
if(arr[i]==tmp[last])
{
idx = i;
break;
}
}
ans.push_back(idx+1);
ans.push_back(last+1);
reverse(arr.begin(), arr.begin()+idx+1);
reverse(arr.begin(), arr.begin()+last+1);
last--;
}
}
return ans;
}
};
3.3.5 字符串乘法
题解:
时间复杂度:O(mn)
空间复杂度:O(m+n)
class Solution {
public:
string multiply(string num1, string num2) {
if(num1=="0"||num2=="0")
{
return "0";
}
int m = num1.size();
int n = num2.size();
vector<int> str(m+n, 0);
for(int i=0;i<m;i++)
{
for(int j=0;j<n;j++)
{
int a = num1[i] - '0';
int b = num2[j] - '0';
int res = a*b;
str[i+j+1] += res; // 两个数乘积,都累加在i+j+1上
int idx = i+j+1;
while(str[idx]>=10) // 说明当前有进位,不断向前传递进位
{
int carry = str[idx]/10;
str[idx]%=10;
idx--;
str[idx]+=carry;
}
}
}
int i=0;
for(;i<m+n;i++)
{
if(str[i]!=0) // 去掉前缀0
{
break;
}
}
string ans="";
while(i<m+n)
{
ans+=str[i]+'0';
i++;
}
return ans;
}
};
3.3.6 计算器
1、基本计算器 leetcode
题解:
时间复杂度:O(n)
空间复杂度:O(n)
class Solution {
public:
int calculate(string s) {
int n = s.size();
long long num=0, res=0, curRes=0;
char preOps='+'; // 记录前一个运算符
for(int i=0;i<n;i++)
{
char c = s[i];
if(isdigit(c))
{
num=num*10+c-'0';
}else if(c=='(')
{
int j=i;
int cnt=0; // 计算括号层数
for(;i<n;i++)
{
if(s[i]=='(')
cnt++;
if(s[i]==')')
cnt--;
if(cnt==0)
break;
}
num = calculate(s.substr(j+1, i-j-1)); // 递归计算括号里的数值
}
if(c=='+'||c=='-'||c=='*'||c=='/'||i==n-1) // 遇到运算符时,先计算前面的结果
{
switch(preOps){
case '+':
curRes+=num;break;
case '-':
curRes-=num;break;
case '*':
curRes*=num;break;
case '/':
curRes/=num;break;
}
if(c=='+'||c=='-'||i==n-1) // 当前运算符是+、-,那么可以直接计算到结果中,否则要等下一个数字先进行乘除运算
{
res+=curRes;
curRes=0;
}
preOps=c;
num=0;
}
}
return res;
}
};
3.3.6 接雨水问题
1、盛最多水的容器 leetcode
题解:
时间复杂度:O(n)
空间复杂度:O(1)
class Solution {
public:
int maxArea(vector<int>& height) {
int i=0, j=height.size()-1;
int ans=0;
while(i<j)
{
int min_h = min(height[i], height[j]);
ans = max(ans, min_h*(j-i));
if(height[i]<height[j])
{
i++;
}else{
j--;
}
}
return ans;
}
};
2、接雨水 leetcode
题解:
时间复杂度:O(n)
空间复杂度:O(1)
class Solution {
public:
int trap(vector<int>& height) {
int left=0;
int right = height.size()-1;
int leftMAX=0, rightMAX=0;
int ans=0;
while(left<right)
{
// 分别获得左、右的两侧屏障,每次总能获得屏障高度-当前height的雨水
leftMAX = max(leftMAX, height[left]);
rightMAX = max(rightMAX, height[right]);
if(leftMAX<rightMAX) // 这里比较的原因是,要取较小值屏障,这样保证有雨水
{
ans+=leftMAX-height[left];
left++;
}else{
ans+=rightMAX - height[right];
right--;
}
}
return ans;
}
};
3.3.7 括号相关的问题
1、有效的括号 leetcode
题解:
时间复杂度:O(n)
空间复杂度:O(n)
class Solution {
public:
bool isValid(string s) {
stack<char> stk;
for(char c:s)
{
if(c=='(' || c=='[' || c=='{')
{
stk.push(c);
}else{
if(c==')')
{
if(stk.empty() || stk.top()!='(')
{
return false;
}else{
stk.pop();
}
}else if(c==']')
{
if(stk.empty() || stk.top()!='[')
{
return false;
}else{
stk.pop();
}
}else if(c=='}')
{
if(stk.empty() || stk.top()!='{')
{
return false;
}else{
stk.pop();
}
}
}
}
return stk.empty();
}
};
题解:
时间复杂度:O(n)
空间复杂度:O(1)
class Solution {
public:
int minAddTomakeValid(string s) {
int left=0, right=0;
for(char c:s)
{
if(c=='(')
{
left++;
}else{
if(left>0) // 有左括号的时候,才能用右括号抵消
left--;
else{
right++;
}
}
}
return left+right;
}
};
题解:
时间复杂度:O(n)
空间复杂度:O(1)
class Solution {
public:
int minInsertions(string s) {
int ans=0;
int need_right=0;
for(int i=0;i<s.size();i++)
{
if(s[i]=='(')
{
need_right+=2; // 出现一个左括号,说明需要2个右括号
if(need_right%2==1) // 如果右括号为奇数,那么插入一个右括号让他满足抵消左括号。同时右括号的需求量--
{
ans++;
need_right--;
}
}else if(s[i]==')')
{
need_right--;
if(need_right==-1) // 如果右括号需求量=-1,说明前面没有左括号,此时插入一个左括号,那么除了当前的右括号,还需要一个右括号,右括号的需求量=1
{
ans++;
need_right=1;
}
}
}
return ans+need_right;
}
};
3.3.8 完美矩形
题解:
时间复杂度:O(n)
空间复杂度:O(n)
class Solution {
public:
long to_key(int x, int y)
{
return (long)1000007*x+y;
}
void record(unordered_map<long, int>& mp, int x, int y)
{
long key=to_key(x, y); // 二维坐标转化成一维
if(mp.count(key))
{
mp.erase(key);
}else{
mp[key]=1;
}
}
bool isRectangleCover(vector<vector<int>>& rectangles) {
int min_x=INT_MAX, min_y=INT_MAX;
int max_a=INT_MIN, max_b=INT_MIN;
long total_area=0;
unordered_map<long, int> mp;
for(int i=0;i<rectangles.size();i++)
{
int x = rectangles[i][0];
int y = rectangles[i][1];
int a = rectangles[i][2];
int b = rectangles[i][3];
min_x = min(min_x, x);
min_y = min(min_y, y);
max_a = max(max_a, a);
max_b = max(max_b, b);
total_area+=(long)(a - x)*(b - y);
record(mp, x, y);
record(mp, x, b);
record(mp, a, y);
record(mp, a, b);
}
long best_area = (long)(max_a-min_x) * (max_b - min_y);
if(best_area!=total_area) // 大矩形的面积应该是几个矩形的面积和,如果不是,那必然错误
{
return false;
}
if(mp.size()==4 && mp.count(to_key(min_x, min_y)) && mp.count(to_key(min_x, max_b)) && mp.count(to_key(max_a, min_y)) && mp.count(to_key(max_a, max_b))) // 最终完美矩形必须只有4个顶点,且在数组中出现过
{
return true;
}else{
return false;
}
}
};
3.3.9 调度考生的座位
class ExamRoom {
public:
set<int> sit;
int n;
ExamRoom(int N) {
n=N;
}
int seat() {
if(sit.size()==0)
{
sit.insert(0);
return 0;
}
int pre = *sit.begin(); // 第一个有人的位置编号
int idx=0, min_dis=pre;
for(auto v : sit)
{
if((v-pre)/2 > min_dis)
{
idx = (v+pre)/2;
min_dis = (v-pre)/2;
}
pre=v;
}
if(n-1-pre>min_dis)
{
idx=n-1;
}
sit.insert(idx);
return idx;
}
void leave(int p) {
sit.erase(p);
}
};
/**
* Your ExamRoom object will be instantiated and called as such:
* ExamRoom* obj = new ExamRoom(n);
* int param_1 = obj->seat();
* obj->leave(p);
*/
3.3.10 二分查找高效判定子序列
1、判断子序列 leetcode
题解:
时间复杂度:O(n)
空间复杂度:O(1)
class Solution {
public:
bool isSubsequence(string s, string t) {
int i=0, j=0;
while(i<s.size() && j<t.size())
{
if(s[i]==t[j])
{
i++;
j++;
}else{
j++;
}
}
return i==s.size();
}
};
2、匹配子序列的单词数 leetcode
题解:
时间复杂度:O(S.length +
∑
i
w
o
r
d
s
i
.
l
e
n
g
t
h
\sum_i words_i.length
∑iwordsi.length)
空间复杂度:O(words.length)
class Solution {
public:
// 例如,有字符串 S = 'dcaog':
// 初始化 heads = 'c' : ('cat', 'cop'), 'd' : ('dog',);
// 遍历 S[0] = 'd' 后,heads = 'c' : ('cat', 'cop'), 'o' : ('og',);
// 遍历 S[1] = 'c' 后,heads = 'a' : ('at',), 'o' : ('og', 'op');
// 遍历 S[2] = 'a' 后,heads = 'o' : ('og', 'op'), 't': ('t',) ;
// 遍历 S[3] = 'o' 后,heads = 'g' : ('g',), 'p': ('p',), 't': ('t',);
// 遍历 S[0] = 'g' 后,heads = 'p': ('p',), 't': ('t',)。
int numMatchingSubseq(string S, vector<string>& words) {
vector<queue<pair<int, int>>> buckets(26);
for(int i = 0; i < words.size(); i++){
buckets[words[i][0]-'a'].push({i, 0}); // 所有单词根据首字母不同放入不同的桶中,并记录在words中的索引 和 字母在这个单词的位置索引
}
int res = 0;
for(auto c: S){
queue<pair<int, int>> & q = buckets[c-'a'];
for(int i = q.size(); i > 0; i--){
auto [wordindex, posIndex] = q.front();
q.pop();
posIndex++;
if(posIndex == words[wordindex].length()){ // 说明匹配到了一个单词
res++;
}
else{
buckets[words[wordindex][posIndex] - 'a'].push({wordindex, posIndex});
}
}
}
return res;
}
};