目录
- 491. 非递减子序列
- 题目描述
- 题解
- 46. 全排列
- 题目描述
- 题解
- 47. 全排列 II
- 题目描述
- 题解
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
题解
回溯算法解决。首先搭建基本框架:
class Solution
{
private:
vector<int> path;
vector<vector<int>> res;
public:
void backTracking(const vector<int> &nums, int start) {
// 每次将当前非递减子序列加入结果集
if (path.size() > 1)
res.push_back(path);
// 递归出口(纵向遍历)
if (start >= nums.size())
return;
// 横向遍历
for (int i = start; i < nums.size(); ++i) {
// 处理
if (path.empty() || nums[i] >= path.back())
path.push_back(nums[i]);
else
continue;
backTracking(nums, i + 1); // 递归
path.pop_back(); // 回溯
}
}
vector<vector<int>> findSubsequences(vector<int> &nums)
{
backTracking(nums, 0);
return res;
}
};
但是这样会导致重复子序列的问题,例如:
于是思考去重方案。本题和 40. 组合总和 II 一样,都需要对回溯树型结构中 “同一父节点下的同一层” (下面简称 “同一层” )节点值进行去重。那道题使用了 used
数组(详解参见 笔记-40(github) / 笔记-40(csdn) ),但本题不能直接用那个套路,因为那种算法需要先将原数组 排序 从而将重复值放在一起,而本题就是要求原数组中的有序列,自然不能先排序。
考虑用一个 集合 存储当前同一层使用过的节点值,从而避免同一层的节点值重复。由于同一层的节点选取是横向遍历的过程,即某次回溯函数中的 for
循环部分,故可以在此之前新建一个局部的 unordered_set
,就可以方便的记录当前这层用过的节点值了。
扒一张 代码随想录 上的树形图,便于进一步理解:
⚠️ 如果放在回溯函数外,该集合成为全局变量,处理起来会不方便
代码(C++)
class Solution
{
private:
vector<int> path;
vector<vector<int>> res;
public:
void backTracking(const vector<int> &nums, int start)
{
// 每次将当前非递减子序列加入结果集
if (path.size() > 1)
res.push_back(path);
// 递归出口(纵向遍历)
if (start >= nums.size())
return;
// 横向遍历
unordered_set<int> used; // 记录本层节点用过的值
for (int i = start; i < nums.size(); ++i)
{
// 去重
if (used.find(nums[i]) != used.end())
continue;
used.insert(nums[i]);
// 处理
if (path.empty() || nums[i] >= path.back())
path.push_back(nums[i]);
else
continue;
backTracking(nums, i + 1); // 递归
path.pop_back(); // 回溯
}
}
vector<vector<int>> findSubsequences(vector<int> &nums)
{
backTracking(nums, 0);
return res;
}
};
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
中的所有整数 互不相同
题解
回溯算法解决。
基本框架采用经典的 回溯三部曲:处理、递归、回溯 。
去重用 used
数组解决,本题对应的是回溯树形结构中对 同一个树枝上的节点值 进行去重。
回溯三部曲可参考:笔记-回溯算法 I
used
数组去重方法可参考:笔记-回溯算法 II 中的 40. 组合总和 II
代码(C++)
class Solution
{
private:
vector<int> path;
vector<vector<int>> res;
vector<int> used; // 记录数字的使用情况,用于去重
public:
void backTracking(const vector<int> &nums)
{
// 递归出口(纵向遍历)
if (path.size() == nums.size())
{
res.push_back(path);
return;
}
// 横向遍历
for (int i = 0; i < nums.size(); ++i)
{
// 去重
if (used[i])
continue;
used[i] = 1;
path.push_back(nums[i]); // 处理
backTracking(nums); // 递归
path.pop_back(); // 回溯
used[i] = 0;
}
}
vector<vector<int>> permute(vector<int> &nums)
{
used.resize(nums.size());
backTracking(nums);
return res;
}
};
另外这题还有种不用刻意去重的解法:
class Solution
{
private:
vector<int> path;
vector<vector<int>> res;
public:
void backTracking(const vector<int> &nums, int start)
{
// 递归出口(纵向遍历)
if (start >= nums.size())
{
res.push_back(path);
return;
}
// 横向遍历
for (int i = start; i < nums.size(); ++i)
{
swap(path[start], path[i]); // 处理
backTracking(nums, start + 1); // 递归
swap(path[start], path[i]); // 回溯
}
}
vector<vector<int>> permute(vector<int> &nums)
{
path = nums;
backTracking(nums, 0);
return res;
}
};
不过该算法和经典框架有些出入,例如递归时传递的是 start + 1
而不是大多数情况下的 i + 1
或 i
。
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
题解
回溯算法解决。这题在 46. 全排列 的基础上,增加了一个条件: nums
中 可包含重复数字 。于是为了得到 不重复 的全排列结果,在对应的回溯树形结构上, 横向纵向都要去重 :
- 同一树枝上要去重(不能重复使用
nums
相同下标的元素,但是下标不同、值相同是可以的) - 同一父节点下的同一层要去重(不能重复使用相同的节点值,无论该值在
nums
中是否本来就不止一个)
可以说是回溯算法两种常见去重的缝合怪了 ( 🐶 )
本题回溯树形结构见 代码随想录 中这张图:
回溯算法基础可参考:笔记-回溯算法 I
代码(C++)
class Solution
{
private:
vector<int> path;
vector<vector<int>> res;
vector<int> branchUsed; // 记录当前树枝中使用过的元素
public:
void backTracking(const vector<int> &nums)
{
// 递归出口(纵向遍历)
if (path.size() == nums.size())
{
res.push_back(path);
return;
}
// 横向遍历
unordered_set<int> layerUsed; // 记录当前层中使用过的元素
for (int i = 0; i < nums.size(); ++i)
{
// 去重
if (layerUsed.find(nums[i]) != layerUsed.end())
continue; // 同一层中去重
if (branchUsed[i])
continue; // 同一树枝中去重
layerUsed.insert(nums[i]);
branchUsed[i] = 1;
path.push_back(nums[i]); // 处理
backTracking(nums); // 递归
path.pop_back(); // 回溯
branchUsed[i] = 0;
}
}
vector<vector<int>> permuteUnique(vector<int> &nums)
{
branchUsed.resize(nums.size());
backTracking(nums);
return res;
}
};