LeetCode 78
问题描述:
给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
回溯法中的子集树问题,相当于一个完全n叉树,此题中相当于完全二叉树,共有2(n+1)-1个结点,2n个叶结点,从根节点到叶节点的分支连起来即对应所有的子集。其中假设左子树为1,选中;右子树为0,不选中。
void dfs(int dep,vector<int>& nums)
{
if(dep==n){
int size=track.size();
if(size==0) cout<<"null"<<endl;//空集输出null
else
for(int i=0;i<size;i++) cout<<track[i]<<" ";
cout<<endl;
return ;
}
track.push_back(nums[dep]);//只有两条分支,先选中,再不选中,
dfs(dep+1,nums); //当分支过多时也可以用for循环代替
track.pop_back();
dfs(dep+1,nums);
}
int main()
{
int t;
cin>>n;
vector<int> nums;//只是想试一下传参,可以直接定义成全局变量数组
for(int i=0;i<n;i++){
cin>>t; nums.push_back(t);
}
dfs(0,nums);
return 0;
}
上面的写法是标准的二叉树,每个层都对应的一个数的选中与不选中,在做了洛谷P2404 自然数的拆分和P1157组合的输出之后,有了新的理解,每层的可以不按照一个数是否选中不选中来做,而是采取它们使用的前驱起始的做法,实现输出的字典序排列:这样写出来第一层可以认为是起始null,第二层是n叉树,后面随着结点的大小叉树在变。
该树的特征:子结点的下标一定要大于父结点的下标;树的高度不一定了;每次判定达到数组最后一个下标结束;每个结点对应一个子集(包括null结点)。
树的结构见下图!
#include <bits/stdc++.h>
using namespace std;
int n,nums[100];
vector<int> track;
void dfs(int pre)
{
if(pre==0) cout<<"null"<<endl;
else{
for(int i=0;i<track.size();i++) printf("%3d",track[i]);
cout<<endl;
}//每过一层的一个结点就要输出一次。
for(int i=pre;i<n;i++){
//这也是递归结束的条件
track.push_back(nums[i]);
dfs(i+1);
track.pop_back();
}
}
int main()
{
cin>>n;
for(int i=0;i<n;i++) cin>>nums[i];
sort(nums,nums+n);//为了实现字典序输出,先排好序
dfs(0);
return 0;
}
LeetCode 46
问题描述:
给定一个没有重复数字的数组nums,返回其所有可能的全排列。
回溯法中的排列树问题,假设数组长度为n,则有 n! 个叶节点,对应 n! 个解
#include <bits/stdc++.h>
using namespace std;
int len;
vector<int> track;
int nums[1000],vis[1000];//全局变量,默认初始化为0
void dfs(int dep)
{
if(dep==len){
for(int i=0;i<len;i++) cout<<track[i]<<" ";
cout<<endl; return ;
}
for(int i=0;i<len;i++){
if(vis[i]==0){
track.push_back(nums[i]);
vis[i]=1;
dfs(dep+1);
track.pop_back();
vis[i]=0;
}
}
}
int main()
{
cin>>len;
for(int i=0;i<len;i++) cin>>nums[i];//数组nums可能无序,最好排个序输出好看
dfs(0);
return 0;
}
最好再main函数里面加一个sort函数对nums数组进行从小到大的排序,这样通过上面方法的搜索可以实现按照字典序的输出。
再来看回溯法用正常递归的一种解法,这种不满足字典序输出:
class Solution {
int len;
vector<vector<int> >ans;
public:
vector<vector<int>> permute(vector<int>& nums) {
len=nums.size();
Perm(0,nums);
return ans;
}
void Perm(int dep,vector<int>& nums){
if(dep==len){
ans.push_back(nums); return ;}
for(int i=dep;i<len;i++){
swap(nums[i],nums[dep]);
Perm(dep+1,nums);
swap(nums[i],nums[dep]);
}
}
};
以nums[]={1,2,3}为例,实现Perm(1,2,3)的递归可展开为:
Perm{1,2,3}=1 Perm{2,3}+2 Perm{1,3}+3 Perm{1,2}……依次递推下去
LeetCode 90 子集2
问题描述:
给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
Input
[1,2,2]
Output
[2],
[1],
[1,2,2],
[2,2],
[1,2],
[]

当不是该层的第一个结点时,如果其他结点出现重复,那么后一个结点的分支必然包括在前面重复的一个结点的分支之中。
因此只需要在上面子集1的思路上再多加一个条件判断即可:if(nums[i] != nums[i-1])
如果是第一个结点,它前面没有结点,可直接加入不用判断if条件
#include <bits/stdc++.h>
using namespace std;
int n,nums[100];
vector<int> track;
void dfs(int pre)
{
if(pre==0) cout<<"null"<<endl;
else{
for(int i=0;i<track.size();i++) printf("%3d",track[i]);
cout<<endl;
}
for(int i=pre;i<n;i++)//这也是递归结束条件
if(i==pre||nums[i]!=nums[i-1]){
//如果i==pre为1后面就不判断了,直接条件成立,对应第一个先进入的情况
track.push_back(nums[i]); //如果i!=pre,即为0,再判断nums[i]与nums[i-1]进行剪枝
dfs(i+1);
track.pop_back();
}
}
int main()
{
cin>>n;
for(int i=0;i<n;i++) cin>>nums[i];
sort(nums,nums+n);//先排序好实现字典序输出
dfs(0);
return 0;
}
LeetCode 47 全排列2
问题描述:
给定一个可包含重复数字的序列,返回所有不重复的全排列。
Input
[1,1,2]
Output
[1,1,2],
[1,2,1],
[2,1,1]

黑色的是正常写法的全排列树形表示,由于vis数组的出现我们可以认为第1层树为n叉,第2层树为n-1叉,直到第n层,只剩下1叉,即该路线被视为一种排列。即每往下一层,认为从该数组中"删去"一个数(上层的结点),求剩下的数构成的"新数组"的全排列。
但是当数组中出现重复数字时这样写存在冗余,我们可以观察到,在每一层中,如果后一个数与前一个数一样的话,那么其接下来的分支也是一样的,所以应当删去,因此还采取像子集2那样的思路,判断每一层分支下nums[i]与nums[i-1]是否相等,不相等则递归。
但是这里的下标i-1的含义有些不一样,因为全排列的话,我们不考虑已加入到track数组的数,需要考虑的是新数组,即原数组nums中间可能有些值不作为结点考虑:nums[i-1]可能已加入到track数组中了,不需考虑,其真实前一个结点应是nums[i-2]或者更靠前的别的值。因此我们需要记录这个前结点进行比对:引入pre变量。
以上考虑的前提:数组nums有序,才能保证nums[i]和nums[i-1]比对
#include <bits/stdc++.h>
using namespace std;
int len;
vector<int> track;
int nums[100],vis[100];//全局变量,默认初始化为0
void dfs(int dep)
{
if(dep==len){
for(int i=0;i<len;i++) cout<<track[i]<<" ";
cout<<endl; return ;
}
for(int i=0,pre=-1;i<len;i++) //初始时每个结点的孩子的第一个结点不需考虑,设为-1
if(vis[i]==0) //每个结点的第一个孩子结点之后的结点需要和前一个比对
if(pre==-1||nums[i]!=nums[pre]){
track.push_back(nums[i]);
vis[i]=1;
dfs(dep+1);
track.pop_back();
vis[i]=0;
pre=i;//记录下来
}
}
int main()
{
cin>>len;
for(int i=0;i<len;i++) cin>>nums[i];
sort(nums,nums+len);//前提,要有序
dfs(0);
return 0;
}