LeetCode46. 全排列
https://leetcode-cn.com/problems/permutations/
给定一个 没有重复 数字的序列,返回其所有可能的全排列。
输入: [1,2,3]
输出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]
思路
回溯模板:
void backtracking(选择列表,路径,结果)
{
if (终止条件)
{
存放结果;
return;
}
for (在选择列表中选择)
{
处理节点;
backtracking(选择列表,路径,结果);
回溯,撤销处理结果
}
}
所有用回溯解决的问题都可以抽象为树形结构:
回溯三部曲:
1.递归函数的返回值和参数
首先排列是有序的,也就是说[1,2] 和[2,1] 是两个不同的排序,这和组合有所不同。
可以看出元素1在[1,2]中已经使用过了,但是在[2,1]中还要在使用一次1,所以处理排列问题就不用使用startIndex了。但排列问题需要一个used数组,记录此时path里有哪些元素已经使用了,一个排列里一个元素只能使用一次,如上图橘黄色部分所示。
vector<vector<int>> result; //存放所有符合条件的路径的结果集合
vector<int> path; //存放符合条件的路径
void backtrack(vector<int>& nums, vector<bool>& used)
2.递归函数的终止条件
可以看出到达叶子节点,就是递归函数的终止条件。
当收集元素的数组path的大小达到和数组nums一样大的时候,说明找到了一个全排列,也表示到达了叶子节点。
if (path.size() == sz)
{
result.push_back(path);
return;
}
3.搜索过程
我们定义的backtrack函数其实就像一个指针,在树中游走同时正确维护每个节点的属性。每当走到叶子节点(即碰到递归函数的终止条件),就保存符合条件的路径,返回并回溯。
//控制树的横向遍历
for (int i = 0; i < sz; ++i)
{
//path里已经使用的元素,直接跳过
if (used[i]) continue;
used[i] = true;
path.push_back(nums[i]);
backtrack(nums, used); //递归:控制树的纵向遍历
used[i] = false;
path.pop_back();
}
代码
class Solution
{
private:
vector<vector<int>> result; //存放所有符合条件的路径的结果集合
vector<int> path; //存放符合条件的路径
void backtrack(vector<int>& nums, vector<bool>& used)
{
int sz = nums.size();
if (path.size() == sz)
{
result.push_back(path);
return;
}
//控制树的横向遍历
for (int i = 0; i < sz; ++i)
{
//path里已经使用的元素,直接跳过
if (used[i]) continue;
used[i] = true;
path.push_back(nums[i]);
backtrack(nums, used); //递归:控制树的纵向遍历
used[i] = false;
path.pop_back();
}
}
public:
vector<vector<int>> permute(vector<int>& nums)
{
//used数组存放bool类型的元素,初始值都为false表示数组nums的元素都没被访问过
vector<bool> used(nums.size(), false);
backtrack(nums, used);
return result;
}
};
LeetCode47. 全排列II
https://leetcode-cn.com/problems/permutations-ii/
给定一个可包含重复数字的序列 nums
,按任意顺序 返回所有不重复的全排列。
输入:nums = [1,1,2]
输出:
[[1,1,2],
[1,2,1],
[2,1,1]]
思路
本题与LeetCode46. 全排列的区别是:本题数组nums的元素是有重复的,但不能有重复的组合。
所以本题的大体思路与LeetCode46. 全排列是相同的,重点是要去重。所谓去重,其实就是使用过的元素不能重复选取。
用回溯解决的问题可以抽象为树形结构,那么“使用过”在这个树形结构上是有两个维度的,一个维度是同一树枝上使用过,一个维度是同一树层上使用过。
根据题意:元素在同一个组合内是可以重复的,但两个组合不能相同。同一树层上的是两个组合里的元素,所以我们要去重的是同一树层上的“使用过”;而同一树枝上的都是一个组合里的元素,所以不用去重。
要在代码层面实现以上的去重逻辑,就需要先对数组进行排序,然后定义一个bool类型数组used。这个数组的元素初始值都为false表示对应的candidate的元素都没被访问过。如果nums[i] == nums[i - 1]
并且 used[i - 1] == false
,就说明:前一个树枝,使用了nums[i - 1],也就是说同一树层使用过nums[i - 1],此时for循环里应该做continue的操作。
所有用回溯解决的问题都可以抽象为树形结构:
代码
class Solution
{
private:
vector<vector<int>> result; //存放所有符合条件的路径的结果集合
vector<int> path; //存放符合条件的路径
void backtrack(vector<int>& nums, vector<bool>& used)
{
int sz = nums.size();
if (path.size() == sz)
{
result.push_back(path);
return;
}
//控制树的横向遍历
for (int i = 0; i < sz; ++i)
{
if (i >= 1 && nums[i] == nums[i-1] && used[i-1] == false) continue;
//path里已经使用的元素,直接跳过
if (used[i]) continue;
used[i] = true;
path.push_back(nums[i]);
backtrack(nums, used); //递归:控制树的纵向遍历
used[i] = false;
path.pop_back();
}
}
public:
vector<vector<int>> permuteUnique(vector<int>& nums)
{
sort(nums.begin(), nums.end());
//used数组存放bool类型的元素,初始值都为false表示数组nums的元素都没被访问过
vector<bool> used(nums.size(), false);
backtrack(nums, used);
return result;
}
};