题目描述
输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。
基本思路
采用插空的方法:每次选一个字符,在剩余字符串中进行插空。
例如:有字符串”abc”,取出a,剩余”bc”,剩余部分可以形成三个间隔( )b( )c( )或 ( )c( )b( ) 一共两种方案, 然后将a插入到3个空中,所以总共有6种方案。
JavaScript实现
完整代码如下:
function Permutation(str){
if(str.length == 0){
return [];
}
var result = [];
if(str.length == 1){
return [str];
}else{//把str分为两部分,第一部分为第一个字母str[0],第二部分为剩余的字符串str.slice(1),把Permutation(str.slice(1))作为一个已知量。
var rest = Permutation(str.slice(1));//递归,Permutation(str.slice(1))表示剩余部分字符串的全排列。
for (var j = 0; j < rest.length; j++) {//插空
for (var k = 0; k < rest[j].length+1; k++) {
var temp = rest[j].slice(0,k)+str[0]+rest[j].slice(k);
result.push(temp);
}
}
//去掉result中重复的元素
var res = [];
for(var k = 0;k<result.length;k++){
if(res.indexOf(result[k]) === -1){
res.push(result[k]);
}
}
return res.sort();
}
}
代码分析
确定用递归的方法来解决问题是关键的一点。类似于数学归纳法:
1. 证明当n=1时命题成立。
2. 假设n=m时命题成立,那么可以推导出在n=m+1时命题也成立。
假设我们已经知道了n-1的输出,要由这个输出得出n的输出。在这个问题里,n-1的输出,对应着长度比当前输入的字符串少1的字符串。也就是说,假如我们已经知道了“abc”的全排列输出的集合,现在再给你一个“d”,要怎样得出新的全排列呢?
很简单,只要对于集合中每一个元素,把d插入到任意相邻字母之间(或者头部和尾部),就可以得到一个新的排列。例如对于元素“acb”,插入到第一个位置,即可得到“dacb”,插入其余位置,可得到“adcb”,“acdb”,“acbd”。容易证明这样形成的新元素不会有重复。
我们把原字符串分成两部分,第一部分为字符串的第一个字符即str[0]
,第二部分为剩余的字符串即str.slice(1)
。那么我们只要知道剩余字符串的全排列,然后将第一个字符插入剩余字符串全排列的间隔即可。根据以上的假设,现在可以把 Permutation(str.slice(1))
作为一个已知量看待。注意:代码中的rest[j].length+1
这里必须加上1,因为slice()方法的截取范围是“左闭右开”区间。
通过上面的方法,可以得到所有排列组合的情况。然而,对于"aab"
得到的结果是["aab","aab","aba","aba","baa","baa"]
,所以还需要一个去重操作。
参考文档
全排列算法的JS实现 - 迷路的约翰