代码随想录算法训练营22天|491.非递减子序列、46.全排列、47.全排列 II
LeetCode491
2025-02-22 18:15:40 星期六
题目描述:力扣491
文档讲解:代码随想录(programmercarl)491. 非递减子序列
视频讲解:《代码随想录》算法视频公开课:回溯算法精讲,树层去重与树枝去重 | LeetCode:491.递增子序列
代码随想录视频内容简记
这个题和之前的组合总和Ⅱ还有子集Ⅱ去重的方式不一样,因为本题不能对数组进行排序,可以看力扣的示例或者k哥的讲解
另外就是要确保输出的子序列的位数是大于等于2的
本题需要对两种情况做去除:
-
[4, 7]和6,就是新添加的元素小于
path.back()
的 -
对树层中出现重复元素的,比如,第一层递归的[4, 7, 6]和最后的7

梳理
-
确定函数的参数和返回值
-
确定递归的终止条件
-
确定单层递归的逻辑。这里是新定义了一个uset用来添加存放过的元素,使其在同层回溯的时候,可以用来检查有没有添加过,如果没有,那么就可以添加,如果有就不能添加了。uset的使用逻辑和used数组是差不多的,所以,uset也可以用来解决组合总和Ⅱ和子集Ⅱ。
大致代码内容
-
if (path.size() >= 2) result.push_back(path)
-
确定单层递归的逻辑,
unordered_set<int> uset
,之后需要做判断,if (uset.find(nums[i]) != uset.end() || nums[i] < path.back() && !path.empty()) continue
。uset.insert(nums[i])
关于uset,他就是在每次进入递归后,都会新建一个新的uset来管下一层的递归中是否有重复元素,所以在回溯的时候并不用erase

LeetCode测试
点击查看代码
class Solution {
private:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& nums, int index) {
if (path.size() >= 2) {
result.push_back(path);
}
if (index == nums.size()) return;
unordered_set<int> uset;
for (int i = index; 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();
}
}
public:
vector<vector<int>> findSubsequences(vector<int>& nums) {
backtracking(nums, 0);
return result;
}
};
LeetCode46
题目描述:力扣46
文档讲解:代码随想录(programmercarl)46.全排列
视频讲解:《代码随想录》算法视频公开课:组合与排列的区别,回溯算法求解的时候,有何不同?| LeetCode:46.全排列
代码随想录视频内容简记
首先是看到这道题的思路,既然是排列,那么我首先联想到的就是之前在39组合总和那道题中记录的错写了for循环的初始条件遇到的排列情况——要点1,那么也就是即时取到后面的数,前面的数也可以重复取。如果是这样的话,index就会失效,那么递归中的index参数也就不复存在了。那只有一个nums
参数行不行?
这里注意一下,如果写成这样的话,忘记了写return,他会一直遍历[1, 1, 1],一定会栈溢出,
void backtracking(vector<int>& nums) {
if (path.size() == nums.size()) {
result.push_back(path);
}
for (int i = 0; i < nums.size(); i++) {
path.push_back(nums[i]);
backtracking(nums);
path.pop_back();
}
}

如果是
void backtracking(vector<int>& nums) {
if (path.size() == nums.size()) {
result.push_back(path);
return;
}
for (int i = 0; i < nums.size(); i++) {
path.push_back(nums[i]);
backtracking(nums);
path.pop_back();
}
}
就会取重复元素,也不符合条件。

所以,要做的只是去重
梳理
-
确定递归函数的参数和返回值。
-
确定递归的终止条件。
-
确定单层递归的逻辑。这里仍旧是用到了used数组,在之前的40组合总和Ⅱ和子集Ⅱ使用used数组是为了做树层去重。那么在本题中也是树层去重吗?非也,是树枝去重,也就是我们要用used来完成之前的index的任务,完成他的遗愿。这还是第一次用used数组完成树枝去重。

大致代码内容
-
终止条件
if (path.size() == nums.size())
-
在单层递归中,如何用used数组来做树枝去重?其实就是每进入一层递归的时候,判断used[i]是否用过,如果用过,就直接跳过。
if (used[i] == true) continue
,这个不如树层去重繁琐
LeetCode测试
整体代码不难
点击查看代码
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++) {
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) {
vector<bool> used(nums.size(), false);
backtracking(nums, used);
return result;
}
};
LeetCode47
题目描述:力扣47
文档讲解:代码随想录(programmercarl)47.全排列 II
视频讲解:《代码随想录》算法视频公开课:回溯算法求解全排列,如何去重?| LeetCode:47.全排列 II
要点
这道题和之前的唯一的区别就在于多了重复的元素,那么很简单,直接加上树层去重就好了
对了,记得对数组进行排列哈😉
LeetCode测试
点击查看代码
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++) {
if (used[i] == true) continue;
if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) continue;
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) {
sort(nums.begin(), nums.end());
vector<bool> used(nums.size(), false);
backtracking(nums, used);
return result;
}
};
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构