接着上一章的回溯问题来讲,没有看过的小伙伴可以查看回溯(子集和组合),本章我们看回溯中的另一大问题——排列问题
一、全排列,给定一个 没有重复数字的序列,返回其所有可能的全排列。
输入: [1,2,3]
输出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]
①递归树
(最下面的叶子节点,红色【】中的就是要求的结果)
然后我们来回想一下,整个问题的思考过程,这棵树是如何画出来的。首先,我们固定1,然后只有2、3可选:如果选2,那就只剩3可选,得出结果[1,2,3];如果选3,那就只剩2可选,得出结果[1,3,2]。再来,如果固定2,那么只有1,3可选:如果选1,那就只剩3,得出结果[2,1,3]…
有没有发现一个规律:如果我们固定了(选择了)某个数,那么他的下一层的选择列表就是——除去这个数以外的其他数!!比如,第一次选择了2,那么他的下一层的选择列表只有1和3;如果选择了3,那么他的下一层的选择列表只有1和2,那么这个时候就要引入一个used数组来记录使用过的数字,算法如下:
void backtrack(vector& nums,vector &used,vector & path)//你也可以把used设置为全局变量
②找结束条件
if(path.size()==nums.size())
{
res.push_back(path);
return;
}
③找准选择列表
for(int i=0;iif(!used[i])//从给定的数中除去用过的,就是当前的选择列表 { } }
④判断是否需要剪枝
不需要剪枝,或者你可以认为,!used[i]已经是剪枝
⑤做出选择
for(int i=0;iif(!used[i])//从给定的数中除去用过的,就是当前的选择列表 { path.push_back(nums[i]);//做选择 used[i]=true;//设置当前数已用 backtrack(nums,used,path);//进入下一层 } }
⑥撤销选择,整体代码如下:
void backtrack(vector& nums,vector &used,vector & path)//used初始化为false { if(path.size()==nums.size()) { res.push_back(path); return; } for(int i=0;i if(!used[i])//如果没用过 { path.push_back(nums[i]);//做选择 used[i]=true;//设置当前数已用 backtrack(nums,used,path);//进入下一层 used[i]=false;//撤销选择 path.pop_back();//撤销选择 } } }
总结:可以发现“排列”类型问题和“子集、组合”问题不同在于:“排列”问题使用used数组来标识选择列表,而“子集、组合”问题则使用start参数。
二、全排列 II(剪枝思想)
给定一个可包含重复数字的序列,返回所有不重复的全排列。
输入: [1,2,2]
输出:
[
[1,2,2],
[2,1,2],
[2,2,1]
]
与子集和组合问题一样,首先需要对题目给出的数组nums排序,让重复的元素排在一起,在if(i>start&&nums[i]==nums[i-1]),基础上修改为if(i>0&&nums[i]==nums[i-1]&&!used[i-1]),语义为:当i可以选第一个元素之后的元素时(因为如果i=0,即只有一个元素,哪来的重复?有重复即说明起码有两个元素或以上,i>0),然后判断当前元素是否和上一个元素相同?如果相同,再判断上一个元素是否能用?如果三个条件都满足,那么该分支一定是重复的,应该剪去。
本题与第一题除了剪枝外思路一样,这里不再赘述。
整体代码如下:
void backtrack(vector& nums,vector &used,vector & path)//used初始化全为false { if(path.size()==nums.size()) { res.push_back(path); return; } for(int i=0;i if(!used[i]) { if(i>0&&nums[i]==nums[i-1]&&!used[i-1])//剪枝,三个条件 continue; path.push_back(nums[i]);//做选择 used[i]=true;//设置当前数已用 backtrack(nums,used,path);//进入下一层 used[i]=false;//撤销选择 path.pop_back();//撤销选择 } } }
三、字符串的全排列(剪枝思想)
输入一个字符串,打印出该字符串中字符的所有排列。你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。
示例:
输入:s = “abc”
输出:[“abc”,“acb”,“bac”,“bca”,“cab”,“cba”]
本题与第二题思路一模一样,只是将nums数组换成了字符串。直接上代码:
//vectorres为全局变量,表示最终的结果集,最后要返回的 class Solution { public: void backtrack(string s,string& path,vector & used)//used数组 { if(path.size()==s.size()) { res.push_back(path); return; } for(int i=0;i if(!used[i]) { if(i>=1&&s[i-1]==s[i]&&!used[i-1])//判重剪枝 continue; path.push_back(s[i]); used[i]=true; backtrack(s,path,used); used[i]=false; path.pop_back(); } } } vector permutation(string s) { if(s.size()==0) return{}; string temp=""; sort(s.begin(),s.end()); vector used(s.size()); backtrack(s,temp,used); return res; } };
总结:“排列”类型问题和“子集、组合”问题不同在于:“排列”问题使用used数组来标识选择列表,而“子集、组合”问题则使用start参数。另外还需注意两种问题的判重剪枝!!



