全排列系列问题(持续更新... ...)
46. 全排列
给定一个 没有重复 数字的序列,返回其所有可能的全排列。
示例:
输入: [1,2,3]
输出: [ [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1] ]
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/permutations
经典的一道回溯算法题,我们需要找出所有可能的排列组合,共有 n!种排列的方式。
图解↓
回溯算法代码:
class Solution {
public:
vector<vector<int>> result;//结果集
vector<int> path;//访问过的元素:标记数组
void backTrack(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]) continue;//元素被使用了,跳过
used[i] = 1;//未使用,标记为1
path.push_back(nums[i]);//当前元素放入path中
backTrack(nums, used);
path.pop_back();
used[i] = 0;
}
}
vector<vector<int>> permute(vector<int>& nums) {
result.clear();
path.clear();
vector<bool> used(nums.size(), false);
backTrack(nums, used);
return result;
}
};
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]]
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/permutations-ii
思路:
与上题不同的是,数组中含有重复元素,这时候为避免重复我们需要对其进行去重操作,首先我们需要对数组进行排序,保证重复元素可以被我们的剪枝条件滤出(排序后相同元素一定是相连的),具体实现见如代码:
class Solution {
public:
pair<int, int> HolandNationalFlag(vector<int>& nums, int start ,int end) {//荷兰国旗法快速排序
int left = start - 1, right = end + 1;
int flag = nums[start];
int index = start;
while (index < right) {
if (nums[index] == flag)
index++;
else if (nums[index] < flag)
swap(nums[index++], nums[++left]);
else
swap(nums[index], nums[--right]);
}
return make_pair(left, right);
}
void QuickSort(vector<int>& nums, int start, int end) {
if (start >= end) return;//只有一个元素,递归结束
pair<int, int> p = HolandNationalFlag(nums, start, end);
QuickSort(nums, start, p.first);//递归调用
QuickSort(nums, p.second, end);
}
vector<vector<int>> result;
vector<int> path;
void backtrack(vector<int>& nums, vector<bool>& used) {
if (path.size() == nums.size()) {
result.push_back(path);
return;
}
for (int i = 0; i < nums.size(); ++i) {
//used[i - 1] == 1 说明同一树枝 nums[i - 1]使用过
//used[i - 1] == 0 ,说明同一层nums[i - 1]使用过
if (i > 0 && nums[i - 1] == nums[i] && !used[i - 1]) {
//去重,同一层中若相同元素已经使用过就跳过当前元素
continue;
}
if (!used[i]) {
used[i] = true;
path.push_back(nums[i]);
backtrack(nums, used);
path.pop_back();
used[i] = false;
}
}
}
vector<vector<int>> permuteUnique(vector<int>& nums) {
result.clear();
path.clear();
vector<bool> used(nums.size(), false);
sort(nums.begin(),nums.end());
backtrack(nums, used);
return result;
}
};
如上使用了数组对每个使用过的元素进行记录,通过判断:
if (i > 0 && nums[i - 1] == nums[i] && !used[i - 1])
//去重,同一层中若相同元素已经使用过就跳过当前元素
消除重复元素。
图中清晰地显示了判断语句的作用: 防止同一拓展节点选择了相同的子节点,对于每个节点来说,其孩子节点不可以选择相同元素,因此另外一种思路: 每层选择孩子节点时使用哈希集合unordered_set记录,如果在选择过程中遇到了相同的元素(之前已经选择过),那么本次选择无效。
上代码:
class Solution {
public:
vector<vector<int>> res;
void dfs(vector<int>& nums, int& n, int depth) {
if (depth == n - 1) {//此时已经完成了[0:n - 2]元素的选择,因此最后一个元素nums[n - 1]已经确定, 退出递归
res.push_back(nums);
return;
}
unordered_set<int> join;
for (int i = depth; i < n; ++i) {
if (join.count(nums[i]) == 1)//在之前已经选择过相同的元素
continue;
join.insert(nums[i]);
swap(nums[i], nums[depth]);//选择 nums[i]
dfs(nums, n, depth + 1);
swap(nums[i], nums[depth]);//再次交换,撤销之前的选择
}
}
vector<vector<int>> permuteUnique(vector<int>& nums) {
int n = nums.size();
dfs(nums, n, 0);
return res;
}
};
三连击
题目描述:
将 1, 2, … , 91,2,…,9 共 9 个数分成 3 组,分别组成 3 个三位数,且使这 3 个三位数构成 1 : 2 : 3 的比例,试求出所有满足条件的 3个三位数。
查看洛谷官网原题描述
思路
使用回溯算法对1~9共九个数字进行全排列,将结果保存于 result 数组中,最后对 result 遍历求出符合条件的值。
直接对所有数字进行回溯以及result遍历,算法调用内存栈层数过高,进程内存达到123MB
其后,我对其进行剪枝操作,剪枝的位置位于对result数组遍历时去除了明显不满足答案的值,峰值依旧到达了123MB,最后将剪枝放到回溯过程中,进程内存减少到了22MB!!!
代码:(未剪枝,剪枝部分代码已打注释)
#include <iostream>
#include <vector>
using namespace std;
vector<vector<int>> result;
vector<int> path;
void backTrack(vector<int>& nums, vector<bool>& used,int depth) {
if (path.size() == nums.size()) {
result.push_back(path);
return;
}
for (int i = 0; i < nums.size(); i++) {
if (used[i]) {
continue;
}
//回溯时进行剪枝↓
/*if (depth == 1 && nums[i] > 3 || depth == 4 && nums[i] > 6 || depth == 7 && nums[i] < 3)
{
continue;
}*/
used[i] = true;
path.push_back(nums[i]);
backTrack(nums, used,depth + 1);
path.pop_back();
used[i] = false;
}
}
vector<int> ans;
void threeAttack() {
int nums1,nums2,nums3;
for (int i = 0; i < result.size(); i++) {
//遍历数组时进行剪枝↓
//剪枝,显然这些情况可以直接排除
/*if (result[i][0] > 3) continue;
else if (result[i][3] > 6) continue;
else if (result[i][6] < 3) continue;*/
nums1 = result[i][0] * 100 + result[i][1] * 10 + result[i][2];
nums2 = result[i][3] * 100 + result[i][4] * 10 + result[i][5];
nums3 = result[i][6] * 100 + result[i][7] * 10 + result[i][8];
if (nums2 == 2 * nums1 && nums3 == 3 * nums1) {
cout << nums1 << " " << nums2 << " " << nums3 << endl;
}
}
}
vector<vector<int>> total_queue(vector<int>& nums) {
vector<bool> used(nums.size(), false);
backTrack(nums, used,1);
threeAttack();
return result;
}
int main() {
/*https://blog.csdn.net/Genius_bin*/
/*author : @nepu_bin*/
vector<int> nums{ 1,2,3,4,5,6,7,8,9 };
total_queue(nums);
return 0;
}
运行效果: