代码随想录算法训练营第二十九天|Leetcode491 递增子序列、Leetcode46 全排列、Leetcode47 全排列 II
● Leetcode491 递增子序列
题目链接:Leetcode491 递增子序列
视频讲解:代码随想录|递增子序列
题目描述:给你一个整数数组 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
● 本题特点
(1)题目描述中找出并返回所有该数组中不同的递增子序列
,我们在path中加入元素时需要考虑遍历元素是否大于等于path中最后一个元素;
(2)递增子序列和子集问题一样,返回的结果在每一个树形结构的分支,但根据题目描述递增子序列中至少有两个元素
,需要在收集结果时进行判断;
(3)数组中可能含有重复元素
则需要对数组进行树层去重,对于[4, 6, 7, 7]这个数组,假设当前path为[4, 6],我们选择第一个7构成[4, 6, 7],选择第二个7依然构成[4, 6, 7],虽然是两个不同的元素,但构成结果集相同,需要去除这种重复结果。
● 解题思路
(1)使用startIndex
用来撇清前面遍历过的元素,假设先选择4,再选择6,构成path[4, 6],如果startIndex标记从下一个元素开始进行下一层递归遍历,就会出现[4, 6]和[6, 4],如果是排列问题当然无可厚非,但子集、子序列问题和组合问题的本质一样,这样两个结果属于相同的组合结果;
(2)解决返回数组中不同递增子序列
的解决很简单,我们在循环中加入条件!path.empty() && nums[i] < path.back()
就可以确保子序列递增;
(3)那么如何解决树层去重的问题呢?因为set里面每个元素只存有一个key值,它支持高效的关键字查询操作,比如检查一个关键字是否在set中。我们可以使用set进行查验是否已经遍历过等值元素,如果有直接continue,否则就将遍历的元素加入到set中。
i.确定回溯函数的参数和返回值:
参数只需要传入数组nums
和用来去除重复组合的startIndex
即可,返回值为void
,直接对定义全局变量进行修改即可;
ii.确定回溯函数的终止条件:
子集、子序列问题不需要设置终止条件,因为在循环条件不满足时,也是遍历完所有元素,和终止条件相满足;
iii.确定回溯函数单层遍历逻辑:
在进入递归函数的时候,就需要对元素结果进行收集,收集结果的同时需要注意对元素个数的判断满足题目要求并在每一层递归中有一个set用来在该树层中进行等值元素去重;
循环遍历数组元素,当元素满足递增且去重的条件则加入到path中,需要在回溯中注意,我们只需要将path最后一个元素弹出即可,不需要将uset中新加入的元素删除,因为uset是每一层都会重新定义的,并不会影响树枝上的结果。
● 代码实现
class Solution {
private:
vector<int> path;
vector<vector<int>> result;
void backtracking(vector<int>& nums, int startIndex)
{
if(path.size() >= 2)
{
result.push_back(path);
}
unordered_set<int> uset;
for(int i = startIndex; 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 全排列
题目链接:Leetcode46 全排列
视频讲解:代码随想录|全排列
题目描述:给定一个不含重复数字的数组 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 中的所有整数 互不相同
● 本题特点
(1)排列问题不同于组合、子集、子序列问题,组合、子集、子序列问题需要去除重复组合的问题,如[4, 7]和[7, 4];而对于排列问题则不需要在意这样的重复,对于排列[4, 7]和[7, 4]是不同的;
(2)我们需要对选择过的元素进行标记,下一层递归中选择未标记元素进行排列。
● 解题思路
我们可以使用used数组对遍历过的元素进行标记,如果遍历到第i个元素时used[i]为真,则元素被选择过直接continue即可。
i.确定回溯函数的参数和返回值:
参数传入原数组nums
和标记元素数组used
,返回值为void;
ii.确定回溯函数的终止条件:
题目描述中返回其 所有可能的全排列
,也就是在叶子结点处才需要收集结果,即条件满足path.size() == nums.size()
;
iii.确定回溯函数单层遍历逻辑:
在遍历元素过程中,当元素遍历过时条件为used[i] == true
,需要continue,当元素未被选择过则将used[i]置为true并将元素加入path中,回溯过程需要重新将used[i]置为false并弹出path中最后一个元素。
● 代码实现
class Solution {
private:
vector<int> path;
vector<vector<int>> result;
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 全排列 II
题目链接:Leetcode47 全排列 II
视频讲解:代码随想录|全排列 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
● 本题特点
在全排列的基础上,因为全排列 II存在重复元素,题目描述中按任意顺序 返回所有不重复的全排列
,我们可以将元素先进行排序,就可以让等值元素相邻,我们需要多加一条判断 if(i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) continue;
即可。
● 解题思路
i.确定回溯函数的参数和返回值:
参数传入原数组nums
和标记元素数组used
,返回值为void;
ii.确定回溯函数的终止条件:
题目描述中返回其 所有可能的全排列
,也就是在叶子结点处才需要收集结果,即条件满足path.size() == nums.size()
;
iii.确定回溯函数单层遍历逻辑:
在遍历元素过程中,当遍历元素重复且used[i - 1]为false,则需要进行树层去重continue,当元素被选择过后也需要continue,当元素未被选择过则将used[i]置为true并将元素加入path中,回溯过程需要重新将used[i]置为false并弹出path中最后一个元素。
● 代码实现
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(i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) continue;
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>> permuteUnique(vector<int>& nums) {
path.clear();
result.clear();
sort(nums.begin(), nums.end());
vector<bool> used(nums.size(), 0);
backtracking(nums, used);
return result;
}
};