今天继续学习回溯算法,主要涉及子集问题。
给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
示例 1:
输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
思路:
1.本题相比起组合与回溯,不同的点在于将整个递归回溯的过程转化为树形结构后,每一个结点都是一个子集。因此相比于组合与回溯只找叶子结点,求子集问题需要找到所有结点。
class Solution {
public:
vector> result;
vector path;
void backtracking(vector& nums, int startIndex){
//因为每个结点都是一个子集,因此在每一层递归的开始就要压入结果数组中
result.push_back(path);
//遇上叶子结点,返回
if(startIndex >= nums.size()){
return;
}
//单层循环
for(int i = startIndex; i < nums.size(); i++){
path.push_back(nums[i]);
backtracking(nums, i + 1);
path.pop_back();
}
}
vector> subsets(vector& nums) {
backtracking(nums,0);
return result;
}
};
启发:
1.因为每个结点都是一个子集,所以在压入结果数组时一定要在每一层递归的起始,随后再判断是否到了叶子结点进行返回。
给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
示例 1:
输入:nums = [1,2,2]
输出:[[],[1],[1,2],[1,2,2],[2],[2,2]]
思路:
1.本题类似于上一题子集和Day25中组合||的结合。原数组中可能存在重复的元素,但是要求最终的每一个子集中不能有重复的元素,是典型的树枝递归允许重复数字但树层遍历不允许重复。我们依旧用一个布尔数组来判断树层遍历时当前元素是否与前一个元素重复。
class Solution {
public:
vector> result;
vector path;
void backtracking(vector& nums, int startIndex, vector& used){
//每层递归之始先把子集压入结果数组中
result.push_back(path);
//到达叶子结点,回溯
if(startIndex >= nums.size()){
return;
}
//单层循环
for(int i = startIndex; i < nums.size(); i++){
//判断树层遍历上是否重复,注意一定是判断used[i - 1]是否为false,否则是树枝上的去重了
if(i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false){
continue;
}
path.push_back(nums[i]);
used[i] = true;
backtracking(nums, i + 1, used);
used[i] = false;
path.pop_back();
}
}
vector> subsetsWithDup(vector& nums) {
vector used(nums.size(), false);
//注意开始递归前一定要先对数组进行排序,方便去重
sort(nums.begin(), nums.end());
backtracking(nums, 0, used);
return result;
}
};
启发:
1.借助本题再一次强化理解了子集问题和去重的思想。子集问题需要搜集树形结构上的每一个结点,因此每层递归最初就要先把当前子集压入结果数组;理解清楚是要在横向树层遍历去重还是在纵向树枝递归去重,理解清楚何时需要判断used[i - 1]为false还是true。