递增子序列
给定一个整型数组, 你的任务是找到所有该数组的递增子序列,递增子序列的长度至少是2。
示例:
- 输入: [4, 6, 7, 7]
- 输出: [[4, 6], [4, 7], [4, 6, 7], [4, 6, 7, 7], [6, 7], [6, 7, 7], [7,7], [4,7,7]]
说明:
- 给定数组的长度不会超过15。
- 数组中的整数范围是 [-100,100]。
- 给定数组中可能包含重复数字,相等的数字应该被视为递增的一种情况。
和之前的问题看上去很像,得到非重复递增子集,但是要注意区别和之前的差异:
在操作子集Ⅱ的问题的时候, 是先对原数组进去排序,再进行回溯操作;
而此处要求是在原数组中寻找递增子序列,所以不能改变数组元素的位置,用和之前一致的操作方法去进行回溯;因为排序之后可能得到原数组中不存在的递增子序列;
以[4,7,6,7]为例:(所有的回溯算法都是深度优先搜索)
vector<int> path;
vector<vector<int>> res;
void backtracking(vector<int>& nums, int startIndex){
if(path.size() >= 2){
res.push_back(path);
//return;//本题其实类似求子集问题,也是要遍历树形结构找每一个节点,可以不加终止条件,startIndex每次都会加1,并不会无限递归
}
unordered_set<int> uset;
for(int i = startIndex; i < nums.size(); i++){
if((!path.empty() && nums[i] < path.back()) //判断子序列是否递增,非递增则进入下层循环
|| uset.find(nums[i]) != uset.end()){//找到元素是否使用过,使用过则进入下层循环
continue;//完成去重操作
}
uset.insert(nums[i]);
path.push_back(nums[i]);
backtracking(nums, i + 1);
path.pop_back();
}
}
整体代码如下:
class Solution {
private:
vector<int> path;
vector<vector<int>> res;
void backtracking(vector<int>& nums, int startIndex){
if(path.size() >= 2){
res.push_back(path);
//return;//本题其实类似求子集问题,也是要遍历树形结构找每一个节点,可以不加终止条件,startIndex每次都会加1,并不会无限递归
}
//unordered_set<int> uset; 可以用数组来实现哈希
int used[201] = {0};
for(int i = startIndex; i < nums.size(); i++){
if((!path.empty() && nums[i] < path.back()) //判断子序列是否递增,非递增则进入下层循环
//|| uset.find(nums[i]) != uset.end()){//找到元素是否使用过,使用过则进入下层循环
|| used[nums[i] + 100] == 1){//因为数组范围为[-100,100];
continue;//完成去重操作
}
//uset.insert(nums[i]);
used[nums[i] + 100] = 1;
path.push_back(nums[i]);
backtracking(nums, i + 1);
path.pop_back();
}
}
public:
vector<vector<int>> findSubsequences(vector<int>& nums) {
path.clear();
res.clear();
backtracking(nums, 0);
return res;
}
};
全排列
给定一个 没有重复 数字的序列,返回其所有可能的全排列。
示例:
- 输入: [1,2,3]
- 输出: [ [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1] ]
由于没有重复数字,所以无须去重;
排列问题其实本质和组合问题的处理方法本质来说是一致的,只是舍弃的节点处理方式有所区别;
排列问题是用used数组树枝去重;
组合问题无重复元素用startIndex去重,有重复元素做数层去重(used数组或者hashset);
如下图所示:
used数组来记录是否使用过此元素,然后删除此条树枝即可;
代码如下:
vector<vector<int>> res;
vector<int> path;
//主函数里定义used vector<bool> used(nums.size(), false);
void backtracking(vector<int>& nums, vector<bool>& used){
if(path.size() == nums.size()){
res.push_back(path);
return;
}
for(int i = 0; i < nums.size(); i++){
if(used[i] == true) continue;
used[i] = true;
path.push_back(nums[i]);
backtracking(nums, used);
path.pop_back();
used[i] = false;
}
}
整体代码如下:
class Solution {
private:
vector<vector<int>> res;
vector<int> path;
void backtracking(vector<int>& nums, vector<bool>& used){
if(path.size() == nums.size()){
res.push_back(path);
return;
}
for(int i = 0; i < nums.size(); i++){
if(used[i] == true) continue;
used[i] = true;
path.push_back(nums[i]);
backtracking(nums, used);
path.pop_back();
used[i] = false;
}
}
public:
vector<vector<int>> permute(vector<int>& nums) {
res.clear();
path.clear();
vector<bool> used(nums.size(), false);
backtracking(nums, used);
return res;
}
};
全排列Ⅱ
给定一个可包含重复数字的序列 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]]
提示:
-
1 <= nums.length <= 8
-
-10 <= nums[i] <= 10
很显然,需要去重了;首先是对数组进行排序,然后再分析树形结构;
很显然,这里用used数组去重即可;
if(i > 0 &&nums[i] == nums[i - 1] && used[i-1] == false) continue;
整体代码如下:
class Solution {
private:
vector<vector<int>> res;
vector<int> path;
void backtracking(vector<int>& nums, vector<bool>& used){
if(path.size() == nums.size()){
res.push_back(path);
return;
}
for(int i = 0; i < nums.size(); i++){
if(i > 0 && nums[i] == nums[i-1] && used[i-1] == false) continue;
if(used[i] == false){
used[i] = true;
path.push_back(nums[i]);
backtracking(nums, used);
path.pop_back();
used[i] = false;
}
}
}
public:
vector<vector<int>> permuteUnique(vector<int>& nums) {
res.clear();
path.clear();
vector<bool> used(nums.size(), false);
sort(nums.begin(), nums.end());
backtracking(nums, used);
return res;
}
};
这里还有个神奇的事,就是used[i-1] == true也可以实现;
以下图为例:
首先是used[i] == false;的判断条件进行判断(树层去重)
然后是used[i] == true;判断条件的遍历逻辑(树枝去重)
很显然树层去重效率更高。