- 📚 博客主页:⭐️这是一只小逸白的博客鸭~⭐️
- 👉 欢迎 关注❤️点赞👍收藏⭐️评论📝
- 😜 小逸白正在备战实习,经常更新面试题和LeetCode题解,欢迎志同道合的朋友互相交流~
- 💙 若有问题请指正,记得关注哦,感谢~
往期文章 :
- LeetCode 剑指 Offer II 链表 专题总结
- LeetCode 剑指 Offer II 哈希表 专题总结
- LeetCode 剑指 Offer II 栈 专题总结
- LeetCode 剑指 Offer II 队列 专题总结
- LeetCode 剑指 Offer II 树(上) 专题总结
- LeetCode 剑指 Offer II 树(下) 专题总结
- LeetCode 剑指 Offer II 堆 专题总结
- LeetCode 剑指 Offer II 前缀树(上) 专题总结
- LeetCode 剑指 Offer II 前缀树(下) 专题总结
- LeetCode 剑指 Offer II 二分查找 专题总结
- LeetCode 剑指 Offer II 排序 专题总结
回溯介绍:
回溯法(backtracking)是优先搜索的一种特殊情况,又称为试探法,常用于需要记录节点状态的深度优先搜索
顾名思义,回溯法的核心是回溯。在搜索到某一节点的时候,如果我们发现目前的节点(及其子节点)并不是需求目标时,我们回退到原来的节点继续搜索,并且把在目前节点修改的状态还原。这样的好处是我们可以始终只对图的总状态进行修改,而非每次遍历时新建一个图来储存状态。
在具体的写法上,它与普通的深度优先搜索一样,都有 [修改当前节点状态]→[递归子节点] 的步骤,
只是多了回溯的步骤,变成了 [修改当前节点状态]→[递归子节点]→[回改当前节点状态]。
这六道题连续的两个都是同一类型的,涵盖了大部分回溯法的基础思路,推荐两个两个比对着看
079. 所有子集(模板题)
题目:
给定一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
示例:
输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
提示:
1 <= nums.length <= 10
-10 <= nums[i] <= 10
nums
中的所有元素 互不相同
思路:
不能回头重复搜索类型,一个数取一次
模板题,我给大家提供了两个模板,一个是带for,选了就加上,一个是选不选的思路
class Solution {
public:
vector<vector<int>> ans;
vector<int> nums;
vector<int> cur;
int n;
vector<vector<int>> subsets(vector<int>& nums) {
this->nums = nums;
n = nums.size();
dfs(0);
return ans;
}
// 模板,不回头地搜索
void dfs(int index) {
ans.emplace_back(cur);
for(int i = index; i < n; i++) {
cur.emplace_back(nums[i]);
dfs(i + 1);
cur.pop_back();
}
}
// 不用for 的模板
void dfs2(int index) {
if(index == nums.size()) {
ans.emplace_back(cur);
return ;
}
// 选
cur.emplace_back(nums[index]);
dfs(index + 1);
cur.pop_back();
// 不选
dfs(index + 1);
}
};
080. 含有 k 个元素的组合
题目:
给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。
示例:
输入: n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]
提示:
1 <= n <= 20
1 <= k <= n
思路:
比上一题 079. 所有子集 多了限制,原本不限制多少个数,取不同的组合
现在限制只能取两个数,所以只要参数加一个个数限制k
判断一下就可以
class Solution {
public:
vector<int> cur;
vector<vector<int>> ans;
int n;
vector<vector<int>> combine(int n, int k) {
this->n = n;
dfs(1, k);
return ans;
}
// 回溯跟上题79差不多,加了个个数限制
void dfs(int num, int k) {
if(k == 0) {
ans.emplace_back(cur);
return ;
}
for(int i = num; i <= n; i++) {
cur.emplace_back(i);
dfs(i + 1, k - 1);
cur.pop_back();
}
}
};
081. 允许重复选择元素的组合
题目:
给定一个无重复元素的正整数数组 candidates 和一个正整数 target ,找出 candidates 中所有可以使数字和为目标数 target 的唯一组合。
candidates 中的数字可以无限制重复被选取。如果至少一个所选数字数量不同,则两种组合是唯一的。
对于给定的输入,保证和为 target 的唯一组合数少于 150 个。
示例:
输入: candidates = [2,3,6,7], target = 7
输出: [[7],[2,2,3]]
提示:
1 <= candidates.length <= 30
1 <= candidates[i] <= 200
candidate
中的每个元素都是独一无二的。1 <= target <= 500
思路:
本题有两个限制:允许重复选择同一元素;选择的元素和为
target
- 第一个限制:我们只需要从原来的从下一个数取变成从本身继续往下取
- 第二个限制:我们每取一个元素就让和减去这个元素,为0的时候表示和刚好为
target
- 大于0时表示超过,不用继续搜索,返回,因为我是在传参时减去的,所以回溯时不需要再次加上该元素
class Solution {
public:
vector<int> candidates;
vector<vector<int>> ans;
vector<int> cur;
int n;
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
this->candidates = candidates;
n = candidates.size();
dfs(0, target);
return ans;
}
// 跟前两题稍有不同,candidates 中的数字可以无限制重复被选取
void dfs(int index, int target) {
// 总和 > target 返回
if(target < 0) return ;
// 总和 == target时加入答案
if(target == 0) {
ans.emplace_back(cur);
return ;
}
for(int i = index; i < n; i++) {
cur.emplace_back(candidates[i]);
// 因为可以重复使用,所以我们从本身位置继续遍历,直到和>=target返回,再加入下一个元素
dfs(i, target - candidates[i]);
cur.pop_back();
}
}
};
082. 含有重复元素集合的组合
题目:
给定一个可能有重复数字的整数数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次,解集不能包含重复的组合。
示例:
输入: candidates = [10,1,2,7,6,1,5], target = 8,
输出:
[
[1,1,6],
[1,2,5],
[1,7],
[2,6]
]
提示:
1 <= candidates.length <= 100
1 <= candidates[i] <= 50
1 <= target <= 30
思路:
本题相较于 081. 允许重复选择元素的组合 有以下改变
一、不能重复取同一元素
二、不能存在重复组合
- 第一个:每次取的都是这个元素的后面元素
dfs(i + 1, target - candidates[i]);
- 第二个:难点在于这个,我们将数组排序一下,方便查重,
sort(candidates.begin(), candidates.end());
在相邻一样的元素时选择跳过if(i - 1 >= index && candidates[i - 1] == candidates[i]) continue;
比如[1,2,2,2,5],选了第一个 2,变成 [1,2],它的下一选项也是 2,跳过它,因为如果选它,就还是 [1,2]
class Solution {
public:
vector<vector<int>> ans;
vector<int> candidates;
vector<int> cur;
int n;
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
this->n = candidates.size();
// 需要排序,方便遇到相同元素时跳过
sort(candidates.begin(), candidates.end());
//quickSort(candidates, 0, candidates.size() - 1);
this->candidates = candidates;
dfs(0, target);
return ans;
}
void dfs(int index, int target) {
if(target < 0) return ;
if(target == 0) {
ans.emplace_back(cur);
return;
}
for(int i = index; i < n; i++) {
// 当前数跟前一个数一样就不选
if(i - 1 >= index && candidates[i - 1] == candidates[i]) continue;
cur.emplace_back(candidates[i]);
dfs(i + 1, target - candidates[i]);
cur.pop_back();
}
}
// 多写写而已,用了没有比sort函数更快,尴尬
void quickSort(vector<int>& candidates, int l, int r) {
if(l >= r) return ;
int i = l, j = r, mid = candidates[((r - l) >> 1) + l];
do{
while(candidates[i] < mid) i++;
while(candidates[j] > mid) j--;
if(i <= j) swap(candidates[i++], candidates[j--]);
}while(i <= j);
quickSort(candidates, l, j);
quickSort(candidates, i, r);
}
};
083. 没有重复元素集合的全排列
题目:
给定一个不含重复数字的整数数组 nums ,返回其 所有可能的全排列 。可以 按任意顺序 返回答案。
示例:
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
提示:
1 <= nums.length <= 6
-10 <= nums[i] <= 10
nums
中的所有整数 互不相同
思路:
关键字:不含重复数字,所有可能的全排列
全排列就不能跟前几题一样只考虑后面元素,不考虑前面元素了
- 所以我们需要一个标记数组
vis
标记访问过的元素防止重复访问- 每次都从头开始遍历,遍历过的
vis
为true
跳过
class Solution {
public:
vector<vector<int>> ans;
vector<int> cur;
vector<int> nums;
int n;
// 标记访问过的数字
vector<bool> vis;
vector<vector<int>> permute(vector<int>& nums) {
this->nums = nums;
this->n = nums.size();
vis.resize(n, 0);
dfs(0);
return ans;
}
// 相比较于前几题,多了个标记数组vis,并且for都是从0开始,dfs传参不是下标,而是cur中的个数,这样看的话不用传参也行
void dfs(int count) {
if(count == n) {
ans.emplace_back(cur);
return ;
}
// 全排列从0开始,可以回头加数
for(int i = 0; i < n; i++) {
if(vis[i] == true) continue;
cur.emplace_back(nums[i]);
vis[i] = 1;
dfs(count + 1);
vis[i] = 0;
cur.pop_back();
}
}
};
084. 含有重复元素集合的全排列
题目:
给定一个可包含重复数字的整数集合 nums ,按任意顺序 返回它所有不重复的全排列。
示例:
输入:nums = [1,1,2]
输出:
[[1,1,2],
[1,2,1],
[2,1,1]]
提示:
1 <= nums.length <= 8
-10 <= nums[i] <= 10
思路:
关键字:包含重复数字,不重复的全排列。
如果数组中元素一样意味着就会出现重复的全排列,所以我们在剑指 Offer II 083. 没有重复元素集合的全排列基础上去重
剑指 Offer II 082. 含有重复元素集合的组合在这去重基础上加上vis标记为的判定
如果相邻两个元素一样,且前一个元素使用过了,代表这个排列使用过,就不必再继续往后搜索了
if((i - 1 >= 0 && nums[i - 1] == nums[i]) && vis[i-1] == 1) break;
class Solution {
public:
vector<vector<int>> ans;
vector<int> cur;
vector<int> nums;
int n;
vector<bool> vis;
vector<vector<int>> permuteUnique(vector<int>& nums) {
// 排序方便查重
sort(nums.begin(), nums.end());
this->nums = nums;
this->n = nums.size();
vis.resize(n, 0);
dfs();
return ans;
}
// 跟82题很像,但因为这个会往回寻找,所以需要判定一下标记数组
void dfs() {
// 跟上一题一样传个count也行,这里不传了,对比一下
if(cur.size() == n) {
ans.emplace_back(cur);
return ;
}
for(int i = 0; i < n; i++) {
if(vis[i] == 1)
continue;
// 相比较82题,遇到前后两数相等且前一个数已经标记过了就表示后面都重复了
if((i - 1 >= 0 && nums[i - 1] == nums[i]) && vis[i-1] == 1)
break;
cur.emplace_back(nums[i]);
vis[i] = 1;
dfs();
vis[i] = 0;
cur.pop_back();
}
}
};