回溯法来了
要处理重复的情况,比如[4,6,7,7],当选了[4,6],再选了一个7后,最后的7就不能再选了;因为题目要求子序列,因此不能排序,使用使用哈希表来记录每一次回溯过程中都加入了哪些元素;注意判断条件:先判断path.empty()然后或是上nums[i] >= path[path.size() - 1],当这两个其中之一满足时,代表nums[i]满足条件,因此继续判断后续的插入是否成功
AC代码:
class Solution {
public:
vector<vector<int>> ans;
vector<int> path;
vector<vector<int>> findSubsequences(vector<int>& nums) {
backtracing(nums, 0);
return ans;
}
void backtracing(vector<int>& nums, int startIndex) {
if (path.size() >= 2) {
ans.push_back(path);
}
unordered_set<int> used;
for (int i = startIndex; i < nums.size(); ++i) {
if ((path.empty() || nums[i] >= path[path.size() - 1]) &&
(used.insert(nums[i])).second) {
path.push_back(nums[i]);
backtracing(nums, i + 1);
path.pop_back();
}
}
}
};
要求排列,因此我们必须要记录之前都选了哪些值,遍历的时候也得从0开始,通过判断当前值是否在之前已经加入过记录数组,来过滤出还未选择的值 AC代码:
class Solution {
public:
vector<vector<int>> ans;
vector<int> path;
vector<vector<int>> permute(vector<int>& nums) {
vector<bool> used(nums.size());
backtracing(nums, used);
return ans;
}
void backtracing(vector<int>& nums, vector<bool>& used) {
if (path.size() == nums.size()) {
ans.push_back(path);
return;
}
for (int i = 0; i < nums.size(); ++i) {
if (!used[i]) {
used[i] = true;
path.push_back(nums[i]);
backtracing(nums, used);
path.pop_back();
used[i] = false;
}
}
}
};
- 47. 全排列 II 和上一题类似,只不过不能有重复的排列,因此需要去重:先排序,然后在遍历时遇到重复值跳过就好 一般来说:组合问题和排列问题是在树形结构的叶子节点上收集结果,而子集问题就是取树上所有节点的结果。
代码如下:
class Solution {
public:
vector<vector<int>> ans;
vector<int> path;
vector<vector<int>> permuteUnique(vector<int>& nums) {
vector<bool> used(nums.size());
sort(nums.begin(), nums.end());
backtracing(nums, used);
return ans;
}
void backtracing(vector<int>& nums, vector<bool>& used) {
if (path.size() == nums.size()) {
ans.push_back(path);
return;
}
for (int i = 0; i < nums.size(); ++i) {
if (!used[i]) {
used[i] = true;
path.push_back(nums[i]);
backtracing(nums, used);
path.pop_back();
used[i] = false;
// 去重,当这一层已经选了一个值nums[i],
// 那么后面所有和nums[i]相同的值就都不用选了
// 因为,假设我们已经有了排列[a,b],
// 那么以[a,b,nums[i]]为开头的排列结果我们就已经得到了
// 后面再在[a,b]的基础上加入nums[i]就会加入重复结果了
while(i < nums.size() - 1 && nums[i + 1] == nums[i]){
++i;
}
}
}
}
};
// 直接复用used数组来去重的写法
class Solution {
private:
vector<vector<int>> result;
vector<int> path;
void backtracking (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] == true,说明同一树枝nums[i - 1]使用过
// used[i - 1] == false,说明同一树层nums[i - 1]使用过
// 如果同一树层nums[i - 1]使用过则直接跳过
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) {
result.clear();
path.clear();
sort(nums.begin(), nums.end()); // 排序
vector<bool> used(nums.size(), false);
backtracking(nums, used);
return result;
}
};
// 时间复杂度: 最差情况所有元素都是唯一的。复杂度和全排列1都是 O(n! * n) 对于 n 个元素一共有 n! 中排列方案。而对于每一个答案,我们需要 O(n) 去复制最终放到 result 数组
// 空间复杂度: O(n) 回溯树的深度取决于我们有多少个元素