491.递增子序列
题目:
给你一个整数数组 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]]
提示:
1 <= nums.length <= 15
-100 <= nums[i] <= 100
思路:
要解决这个问题,可以使用回溯法。
递归终止条件
- 因为我们需要找出所有长度至少为 2 的递增子序列,所以当
path
的长度大于等于 2 时,就可以将其加入结果集中。
选择元素并递归
- 从当前位置
start
开始,遍历数组中的每个元素。 - 对于每个元素,检查它是否可以添加到当前的
path
中,即检查它是否大于等于path
的最后一个元素(如果path
为空,则无条件添加)。 - 如果可以,将其添加到
path
中,并继续递归调用backtrack
,传递更新后的path
和下一个开始位置。
去重
- 为了避免重复子序列,在同一层递归中使用一个集合
used
来记录已经添加过的元素。如果某个元素在当前递归层已经使用过,则跳过。
回溯
- 完成当前递归层的工作后,回溯时需要将最后添加的元素从
path
中移除,继续进行下一轮递归。
上代码:
class Solution {
public:
vector<vector<int>> findSubsequences(vector<int>& nums) {
vector<vector<int>> res;
vector<int> path;
backtrack(nums, 0, path, res);
return res;
}
private:
void backtrack(vector<int>& nums, int start, vector<int>& path, vector<vector<int>>& res) {
if (path.size() >= 2) {
res.push_back(path);
}
unordered_set<int> used;
for (int i = start; i < nums.size(); ++i) {
// 如果当前元素在这一层已经用过,跳过
if (used.find(nums[i]) != used.end()) continue;
// 如果当前path为空,或者当前元素大于等于path的最后一个元素
if (path.empty() || nums[i] >= path.back()) {
used.insert(nums[i]);
path.push_back(nums[i]);
backtrack(nums, i + 1, path, res);
path.pop_back(); // 回溯
}
}
}
};
46.全排列
题目:
给定一个不含重复数字的数组 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]]
提示:
1 <= nums.length <= 6
-10 <= nums[i] <= 10
nums
中的所有整数 互不相同
思路:
要解决这个全排列问题,同样可以使用回溯法。全排列问题要求生成所有可能的排列组合,因此回溯法非常适合这个场景。
回溯思路
-
递归终止条件
- 当
path
的长度等于nums
的长度时,说明当前排列已经构建完成,将其加入结果集中。
- 当
-
选择元素并递归
- 遍历
nums
数组中的每个元素。 - 如果某个元素尚未使用(
used[i]
为false
),则将其添加到当前的path
中,并标记为已使用,继续递归。
- 遍历
-
回溯
- 完成当前递归层的工作后,回溯时将最后添加的元素从
path
中移除,并将其对应的used
标记为未使用,继续进行下一轮递归。
- 完成当前递归层的工作后,回溯时将最后添加的元素从
上代码:
class Solution {
public:
vector<vector<int>> permute(vector<int>& nums) {
vector<vector<int>> res;
vector<int> path;
vector<bool> used(nums.size(), false);
backtrack(nums, path, used, res);
return res;
}
private:
void backtrack(vector<int>& nums, vector<int>& path, vector<bool>& used, vector<vector<int>>& res) {
if (path.size() == nums.size()) {
res.push_back(path);
return;
}
for (int i = 0; i < nums.size(); ++i) {
if (!used[i]) {
used[i] = true; // 标记为已使用
path.push_back(nums[i]);
backtrack(nums, path, used, res);
path.pop_back(); // 回溯
used[i] = false; // 标记为未使用
}
}
}
};
47.全排列 II
题目:
给定一个可包含重复数字的序列 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
思路:
为了解决包含重复数字的序列的全排列问题,可以使用回溯法,同时加入去重策略,确保生成的排列是唯一的。
回溯思路
-
排序
- 首先对
nums
进行排序。排序的目的是为了方便在回溯过程中跳过重复元素。
- 首先对
-
递归终止条件
- 当
path
的长度等于nums
的长度时,说明当前排列已经构建完成,将其加入结果集中。
- 当
-
选择元素并递归
- 遍历
nums
数组中的每个元素。 - 如果某个元素尚未使用(
used[i]
为false
),则需要进一步检查:- 如果
nums[i]
与nums[i-1]
相同,且used[i-1]
为false
,则跳过这个元素,以避免生成重复的排列。
- 如果
- 如果通过了上述检查,将元素添加到当前的
path
中,并标记为已使用,继续递归。
- 遍历
-
回溯
- 完成当前递归层的工作后,回溯时将最后添加的元素从
path
中移除,并将其对应的used
标记为未使用,继续进行下一轮递归。
- 完成当前递归层的工作后,回溯时将最后添加的元素从
上代码:
class Solution {
public:
vector<vector<int>> permuteUnique(vector<int>& nums) {
vector<vector<int>> res;
vector<int> path;
vector<bool> used(nums.size(), false);
sort(nums.begin(), nums.end());
backtrack(nums, path, used, res);
return res;
}
private:
void backtrack(vector<int>& nums, vector<int>& path, vector<bool>& used, vector<vector<int>>& res) {
if (path.size() == nums.size()) {
res.push_back(path);
return;
}
for (int i = 0; i < nums.size(); ++i) {
if (used[i]) continue;
// 跳过重复元素
if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) continue;
used[i] = true;
path.push_back(nums[i]);
backtrack(nums, path, used, res);
path.pop_back(); // 回溯
used[i] = false;
}
}
};