[声明]:ACWing Y总刷题课总结,回溯部分总结参考了灵神
题号 | 题目链接 |
---|---|
17 | 九键字母组合 |
79 | 单词搜索 |
46 | 全排列 |
47 | 全排列II |
78 | 子集 |
90 | 子集II |
216 | 组合总和 III |
52 | N皇后 II |
37 | 解数独 |
473 | 火柴拼正方形 |
1.题目链接 — LeetCode17. 电话号码的字母组合
方法一:遍历数字
- 解题思路:类似BFS的思路
代码如下
class Solution {
public:
string map[8] = {"abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
vector<string> letterCombinations(string digits) {
if(!digits.size()) return vector<string>();
vector<string> ans(1, "");
for(auto u : digits){
vector<string> now;
for(auto c : map[u - '2']){
for(auto s : ans){
now.push_back(s + c);
}
}
ans = now;
}
return ans;
}
};
方法二:DFS + 回溯
代码如下
class Solution {
public:
string map[8] = {"abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
vector<string> ans;
string s;
void dfs(int n, string digits){ // n为搜索的digits的位数
if(n == digits.size()){
ans.push_back(s); // 遍历完成 加入ans
return ;
}
int num = digits[n]-'0'-2; // num 为 map映射索引值
for(auto u : map[num]){ // 横向遍历 map的字符串
s += u; // 当前遍历的字符加入字符串中
dfs(n+1, digits); // 搜索下一轮
s.pop_back(); // 回溯:恢复原来的状态(删掉末尾)
}
}
vector<string> letterCombinations(string digits) {
if(!digits.size()) return vector<string>();
dfs(0, digits);
return ans;
}
};
2.题目链接 — LeetCode79.单词搜索
代码
class Solution {
public:
int n, m;
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
bool dfs(vector<vector<char>>& board, int x, int y, string& word, int u){
if(board[x][y] != word[u]) return false; // 不匹配 返回false
if(u == word.size() - 1) return true; // 递归出口
char t = board[x][y]; // 设置现场
board[x][y] = '.'; // 不能往回搜素
for(int i = 0; i < 4; i++){
int a = x + dx[i], b = y + dy[i];
if(a >= 0 && a < board.size() && b >= 0 && b < board[0].size()){ // n m 用不了
if(dfs(board, a, b, word, u+1)){
return true;
}
}
}
board[x][y] = t; // 恢复现场
return false;
}
bool exist(vector<vector<char>>& board, string word) {
// if(board.empty() || board[0].empty()) return false;
int n = board.size(), m = board[0].size();
for(int i = 0; i < n; i++){
for(int j = 0; j < m; j++){
if(dfs(board, i, j, word, 0)){
return true;
}
}
}
return false;
}
};
3.题目链接 — LeetCode46. 全排列
示例
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
回溯 + DFS 思路
代码
class Solution {
public:
int n;
vector<bool> st;
vector<vector<int>> ans;
vector<int> path;
void dfs(vector<int>& nums, int u){
if(u == nums.size()){
ans.push_back(path);
return ;
}
for(int i = 0; i < nums.size(); i++){
if(!st[i]){
// 1 设置现场
st[i] = true;
path.push_back(nums[i]);
// 2 搜索
dfs(nums, u + 1);
// 3 恢复现场
path.pop_back();
st[i] = false;
}
}
}
vector<vector<int>> permute(vector<int>& nums) {
n = nums.size();
st = vector<bool>(n);
dfs(nums, 0);
return ans;
}
};
Next_permutation STL库函数
class Solution {
public:
vector<vector<int>> ans;
vector<vector<int>> permute(vector<int>& nums) {
sort(nums.begin(), nums.end());
do{
ans.push_back(nums);
}while(next_permutation(nums.begin(), nums.end()));
return ans;
}
};
4.题目链接 — LeetCode47. 全排列 II
示例
输入:nums = [1,1,2]
输出:[[1,1,2],[1,2,1], [2,1,1]]
思路
- 对于相同的数,人为规定顺序进行搜索(相同的数字,相对顺序不能变)。用start变量来记录上一个相同数存放的位置,如果nums[i+1] == nums[i],则需要把nums[i+1]这个数从start+1, start+2, … , 枚举到n。
代码
class Solution {
public:
vector<bool> st;
vector<int> path;
vector<vector<int>> ans;
vector<vector<int>> permuteUnique(vector<int>& nums) {
int n = nums.size();
sort(nums.begin(), nums.end());
st = vector<bool>(n, false);
path = vector<int>(n);
dfs(nums, 0, 0);
return ans;
}
void dfs(vector<int>& nums, int u, int start){
if (u == nums.size()){
ans.push_back(path);
return;
}
for (int i = start; i < nums.size(); i ++ )
if (!st[i]){
st[i] = true;
path[i] = nums[u];
if (u + 1 < nums.size() && nums[u + 1] != nums[u])
dfs(nums, u + 1, 0);
else
dfs(nums, u + 1, i + 1);
st[i] = false;
}
}
};
4.题目链接 — LeetCode78. 子集
示例
输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
方法一:二进制枚举
代码
class Solution {
public:
vector<vector<int>> subsets(vector<int>& nums) {
vector<vector<int>> ans;
int n = nums.size();
for(int i = 0; i < 1 << n; i++){ // i: 0 ~ 2^n - 1
vector<int> now;
for(int j = 0; j < n; j++){
if(i >> j & 1){ // 如果i的第j位是1: 就选nums[j]
now.push_back(nums[j]);
}
}
ans.push_back(now);
}
return ans;
}
};
类型:子集型回溯
方法二:
对每个元素进行选择: 选 / 不选
代码:
class Solution {
public:
vector<vector<int>> ans;
vector<int> path;
vector<vector<int>> subsets(vector<int>& nums) {
int n = nums.size();
dfs(0, n, nums);
return ans;
}
void dfs(int k, int n, vector<int>& nums){
if(k == n){
ans.push_back(path);
return;
}
// 1.不选
dfs(k+1, n, nums);
// 2.选
path.push_back(nums[k]);
dfs(k+1, n, nums);
path.pop_back();
}
};
类型:子集型回溯
方法三:
枚举每个数选谁
代码:
class Solution {
public:
vector<vector<int>> ans;
vector<int> path;
vector<vector<int>> subsets(vector<int>& nums) {
int n = nums.size();
dfs(0, n, nums);
return ans;
}
void dfs(int k, int n, vector<int>& nums){
ans.push_back(path);
if(k == n){
return;
}
for(int i = k; i < n; i ++){
path.push_back(nums[i]);
dfs(i+1, n, nums);
path.pop_back();
}
}
};
5.题目链接 — LeetCode90. 子集II
示例
输入:nums = [5,2,2,2,3,3]
输出:[[],[5],[3],[3,5],[3,3],[3,3,5],[2],[2,5],[2,3],[2,3,5],[2,3,3],[2,3,3,5],
[2,2],[2,2,5],[2,2,3],[2,2,3,5],[2,2,3,3],[2,2,3,3,5],
[2,2,2],[2,2,2,5],[2,2,2,3],[2,2,2,3,5],[2,2,2,3,3],[2,2,2,3,3,5]]
稍微难想一点,y总真是太nb啦,换我想不出来这种写法。。
代码
class Solution {
public:
vector<vector<int>> ans;
vector<int> path;
void dfs(vector<int>& nums, int u){
if(u == nums.size()){
ans.push_back(path);
return;
}
//计算当前数字个数k
int k = 0;
while(u+k < nums.size() && nums[u+k] == nums[u]) k++;
for(int i = 0; i <= k; i++){ // i为选当前数字的个数(i=0不选, i=1选1个, i=k选k个)
cout<<u+k<<" ";
dfs(nums, u+k);
path.push_back(nums[u]);
}
// 恢复现场, 因为需要重新选数字
for(int i = 0; i <= k; i++) path.pop_back();
}
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
// 计算每个数的个数,然后等价于排列组合选每个数的个数,凑成一个子集
sort(nums.begin(), nums.end());
dfs(nums, 0);
return ans;
}
};
6.0 补充题目 — [LeetCode77. 组合](https://leetcode.cn/problems/combinations/)
示例
给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。
输入:n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]
类型:组合型回溯
方法一:正序枚举
代码
class Solution {
public:
vector<int> path;
vector<vector<int>> ans;
vector<vector<int>> combine(int n, int k) {
dfs(1, k, n); // 从1-n中选k个数
return ans;
}
void dfs(int start, int k, int n){
if(k == 0){
ans.push_back(path);
return;
}
// 剪枝:从i - n 中选数个数必须 >= k
// n - i + 1 >= k
// i <= n+1-k
for(int i = start; i <= n; i++){ // 枚举下一个数选谁
path.push_back(i);
dfs(i+1, k-1, n); // 从 i+1 - n 中选k-1个数
path.pop_back();
}
}
};
方法一:正序枚举 + 剪枝(实测效率最高)
代码
class Solution {
public:
vector<int> path;
vector<vector<int>> ans;
vector<vector<int>> combine(int n, int k) {
dfs(1, k, n); // 从1-n中选k个数
return ans;
}
void dfs(int start, int k, int n){
if(k == 0){
ans.push_back(path);
return;
}
// 剪枝:从i - n 中选数个数必须 >= k
// n - i + 1 >= k
// i <= n+1-k
for(int i = start; i <= n + 1 - k; i++){ // 枚举下一个数选谁
path.push_back(i);
dfs(i+1, k-1, n); // 从 i+1 - n 中选k-1个数
path.pop_back();
}
}
};
方法二:选or不选当前数 + 剪枝
代码:
class Solution {
public:
vector<int> path;
vector<vector<int>> ans;
vector<vector<int>> combine(int n, int k) {
dfs(1, k, n);
return ans;
}
void dfs(int start, int k, int n){
if(k == 0){
ans.push_back(path);
return;
}
// 剪枝:
// n - i + 1 >= k
// i <= n+1-k
if(start > n + 1 - k) return;
// 不选i
dfs(start+1, k, n);
// 选i
path.push_back(start);
dfs(start+1, k-1, n);
path.pop_back();
}
};
6.1 题目链接 — LeetCode216. 组合总数III
示例
输入: k = 3, n = 9
输出: [[1,2,6], [1,3,5], [2,3,4]]
解释:
1 + 2 + 6 = 9
1 + 3 + 5 = 9
2 + 3 + 4 = 9
没有其他符合的组合了。
类型:组合型回溯
方法一:逆序枚举
代码
class Solution {
public:
vector<vector<int>> ans;
vector<int> path;
vector<vector<int>> combinationSum3(int k, int n) {
dfs(k, 1, n); // dfs(枚举k个数, 开始枚举的位置, sum)
return ans;
}
void dfs(int k, int start, int n){
if(k == 0){
if(n == 0) ans.push_back(path);
return;
}
for(int i = start; i <= 9; i++){
path.push_back(i);
dfs(k-1, i+1, n-i);
path.pop_back(); // 恢复现场
}
}
};
方法一:逆序枚举+剪枝
剪枝思路:
如果还要枚举k个数,当前枚举到第i个数时
需要满足:9 - i + 1 >= k ----> i <= 10 - k
例如:需要枚举3个数,当前1~9枚举到了8这个数。k = 3, i = 8, 9 - i + 1 = 2 < 3, 即只剩2个数枚举,故无法满足题意,必须return。
时间复杂度
class Solution {
public:
vector<vector<int>> ans;
vector<int> path;
vector<vector<int>> combinationSum3(int k, int n) {
dfs(k, 1, n); // dfs(枚举k个数, 开始枚举的位置, sum)
return ans;
}
void dfs(int k, int start, int n){
if(k == 0){
if(n == 0) ans.push_back(path);
return;
}
// 9 - i + 1 >= k
// i <= 10 - k
for(int i = start; i <= 10 - k; i++){
path.push_back(i);
dfs(k-1, i+1, n-i);
path.pop_back(); // 恢复现场
}
}
};
方法二:选or不选当前数
代码
class Solution {
public:
vector<vector<int>> ans;
vector<int> path;
vector<vector<int>> combinationSum3(int k, int n) {
dfs(k, 1, n); // dfs(选k个数, 开始枚举的位置, sum)
return ans;
}
void dfs(int k, int start, int n){
// cout << k << " " << start << " "<< n << endl;
if(k == 0){
if(n == 0) ans.push_back(path);
return;
}
// 9 - i + 1 >= k
// i <= 10 - k
if(start > 9) return;
// 不选i
dfs(k, start+1, n);
// 选i
path.push_back(start);
dfs(k-1, start+1, n-start);
path.pop_back(); // 恢复现场
}
};
方法二:选or不选当前数 + 剪枝
剪枝思路同上一个方法,只需要判断start是否<=10-k,如果>10-k则return
代码
class Solution {
public:
vector<vector<int>> ans;
vector<int> path;
vector<vector<int>> combinationSum3(int k, int n) {
dfs(k, 1, n); // dfs(选k个数, 开始枚举的位置, sum)
return ans;
}
void dfs(int k, int start, int n){
// cout << k << " " << start << " "<< n << endl;
if(k == 0){
if(n == 0) ans.push_back(path);
return;
}
// 9 - i + 1 >= k
// i <= 10 - k
if(start > 10 - k) return;
// 不选i
dfs(k, start+1, n);
// 选i
path.push_back(start);
dfs(k-1, start+1, n-start);
path.pop_back(); // 恢复现场
}
};