目录
一、491. 递增子序列
1.题目描述
给你一个整数数组 nums
,找出并返回所有该数组中不同的递增子序列,递增子序列中 至少有两个元素 。你可以按 任意顺序 返回答案。
数组中可能含有重复元素,如出现两个整数相等,也可以视作递增序列的一种特殊情况。
示例 1:
输入:nums = [4,6,7,7] 输出:[[4,6],[4,6,7],[4,6,7,7],[4,7],[4,7,7],[6,7],[6,7,7],[7,7]]
示例 2:
输入:nums = [4,4,3,2,1] 输出:[[4,4]]
2.解题思路
1.题目要求递增的子序列,因此在每次递归backtracking的时候,需要判断第一个元素和path路径中的最后一个元素大小关系(只有大于等于path.back() 才会push进去)。
- 这里要注意,和path.back()比较时,需要先对path做非空判断。如果path是空的,说明当前元素是第一个元素,直接push即可。
2.这里收集结果的条件是,只要path数组中的元素大于1(也就是题目要求的至少有两个元素)。
3.去重操作:
①利用unordered set数组来做去重,如果当前元素在本层中已经出现过,直接continue
②直接利用数组做去重操作,对数组初始化0,使用过的元素就+1。
3.代码实现
class Solution {
public:
vector<int> path;//收集路径元素
vector<vector<int>> result;//结果集
//确定函数参数
void backtracking(const vector<int>& nums,int startIndex){
//只要path中存在了两个及以上的元素,就收集结果
if(path.size() > 1){
result.push_back(path);
}
//确定终止条件
if(startIndex >= nums.size()){
//result.push_back(path);
return;
}
//单层逻辑
//使用unordered set对本层做去重操作
unordered_set<int> uset;
for(int i = startIndex;i < nums.size();i++){
//先判断是否递增,如果path是空,说明当前是第一个元素,无需判断递增关系
//还要判断:如果这个元素在本层使用过,就去重
if(!path.empty() && nums[i] < path.back() || uset.find(nums[i]) != uset.end()){
continue;
}
//收集路径
path.push_back(nums[i]);
uset.insert(nums[i]);
//深度递归
backtracking(nums,i + 1);
//回溯操作
path.pop_back();
}
}
vector<vector<int>> findSubsequences(vector<int>& nums) {
result.clear();
path.clear();
backtracking(nums,0);
return result;
}
};
二、46. 全排列
1.题目描述
给定一个不含重复数字的数组 nums
,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
示例 1:
输入:nums = [1,2,3] 输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
示例 2:
输入:nums = [0,1] 输出:[[0,1],[1,0]]
示例 3:
输入:nums = [1] 输出:[[1]]
2.解题思路
排列问题和组合问题的主要区别就是:举个例子,排列中[1,2] 和 [2,1]是两中结果,而在组合中这就是一种结果。
关键点一:如何确定终止条件?
- 当path数组中的元素个数和nums数组个数一样时,便是收集结果的时候。
关键点二:如何确定单层递归逻辑?
- 这里因为是排列问题,每次for循环的i不是从startIndex开始,而是从0开始
- 使用used数组来反映当前元素是否使用过。
3.代码实现
class Solution {
public:
vector<int> path;
vector<vector<int>> result;//结果集
//确定函数参数
void backtracking(const vector<int>& nums,vector<bool>& used){
//确定终止条件
if(path.size() == nums.size()){
result.push_back(path);
return;
}
//单层逻辑
for(int i = 0;i < nums.size();i++){
if(used[i] == false){
path.push_back(nums[i]);
used[i] = true;
//深度递归
backtracking(nums,used);
//回溯操作
path.pop_back();
used[i] = false;
}
else{
continue;
}
}
}
vector<vector<int>> permute(vector<int>& nums) {
path.clear();
result.clear();
vector<bool> used(nums.size(),false);//定义一个长度和nums一样的数组used,用来判断元素是否已经使用过
backtracking(nums,used);
return result;
}
};
三、47. 全排列 II
1.题目描述
给定一个可包含重复数字的序列 nums
,按任意顺序 返回所有不重复的全排列。
示例 1:
输入:nums = [1,1,2] 输出: [[1,1,2], [1,2,1], [2,1,1]]
示例 2:
输入:nums = [1,2,3] 输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
2.解题思路
- 去重的核心和组合问题一样,先对题目所给数组nums排序,让相邻元素邻近。
- 判断nums[i] 和nums[ i - 1]是否相同(注意:这里判断相同,一定是同一树层的相同才continue,同一树枝的不用continue)
3.代码实现
class Solution {
public:
vector<int> path;
vector<vector<int>> result;//结果集
//确定函数参数
void backtracking(const vector<int>& nums,vector<bool>& used){
//确定终止条件
if(path.size() == nums.size()){
result.push_back(path);
return;
}
//单层逻辑
for(int i = 0;i < nums.size();i++){
//先判断当前元素是否使用过
//还需要判断同一树层,当前元素是否和前一个元素一样,如果一样,直接跳过
//used[i - 1] == flase表示在同一树层使用过
//used[i - 1] == true 表示在同一树枝使用过
if(i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false){
continue;
}
if(used[i] == false){//没有被使用过
path.push_back(nums[i]);
used[i] = true;
//深度递归
backtracking(nums,used);
//回溯操作
path.pop_back();
used[i] = false;
}
}
}
vector<vector<int>> permuteUnique(vector<int>& nums) {
//对nums排序,相邻元素放一块,方便后续去重
sort(nums.begin(),nums.end());
vector<bool> used(nums.size(),false);
backtracking(nums,used);
return result;
}
};