题目:输入一个字符串,打印出该字符串中字符的全部排列。java
例如输入字符串abc,则打印由字符a,b,c所能排列出来的全部字符串:abc,abc,bac,bca,cab,cba面试
咱们求整个字符串的排列,能够当作两步:首先求出全部可能出如今第一 个位置的字符,即把第一个字符和后面全部的字符交换。下图就是分别把第一个字符a和后面的b,c交换的情景。第二步固定第一个字符,求后面全部字符的排 列。这个时候咱们仍把后面的全部字符分红两部分:后面字符的第一个字符,以及这个字符以后的全部字符。而后把第一个字符逐一和它后面的字符交换……算法
注:(a)把字符串分为两部分,一部分是字符串的第一个字符,另外一部分是第一个字符之后的全部字符。接下来咱们求阴影部分的字符串的排列。(b)拿第一个字符和它后面的字符逐个交换。数组
即咱们以三个字符abc为例来分析一下求字符串排列的过程。首先咱们固定第一个字符a,求后面两个字符bc的排列。当两个字符bc的排列求好以后,我 们把第一个字符a和后面的b交换,获得bac,接着咱们固定第一个字符b,求后面两个字符ac的排列。如今是把c放到第一位置的时候了。记住前面咱们已经 把原先的第一个字符a和后面的b作了交换,为了保证此次c仍然是和原先处在第一位置的a交换,咱们在拿c和第一个字符交换以前,先要把b和a交换回来。在 交换b和a以后,再拿c和处在第一位置的a进行交换,获得cba。咱们再次固定第一个字符c,求后面两个字符b、a的排列。app
为方便起见,用123来示例下。123的全排列有12三、13二、21三、23一、3十二、321这六种。首先考虑213和321这二个数是如何得出的。 显然这二个都是123中的1与后面两数交换获得的。而后能够将123的第二个数和每三个数交换获得132。同理能够根据213和321来得231和 312。所以能够知道——全排列就是从第一个数字起每一个数分别与它后面的数字交换。函数
分析到这里,咱们就能够看出,这实际上是很典型的递归思路,因而咱们写出下面的Java代码:优化
package cglib;ui
public class DeleteNode{
spa
public static void main(String[] args) { 递归
char buf[]={'a','b','c'};
perm(buf,0,buf.length-1);
}
public static void perm(char[] buf,int start,int end){
//这个判断很关键,每次递归到最后的时候,就是START每次都等于END的时候,就是要打印出相应的全排列字符串的时候,
if(start==end){//这个判断用于递归到最后的时候输出相应的字符串
for(int i=0;i<=end;i++){
System.out.print(buf[i]);
}
System.out.println();
}
else{//这个else块的做用有1:交换第一个位置的字符,好比第一个位置的全排列字符串所有打印后
//就把第一个字符和第二个交换;2:递归打印每次第一个字符串的全排列字符串;3:每次
//递归的时候都会传递一个字符串数组,最后三行代码就是控制这个字符串数组不变,意思就是
//什么样子传递出去,就什么样子传递回来,一点不能变化,由于最后三行代码不是用于改变
//字符串数组的
for(int i=start;i<=end;i++){
System.out.println("交换前:buf[start="+start+"]="+buf[start]);
System.out.println("交换前:buf[i="+i+"]="+buf[i]);
char temp=buf[start];
//第一次输出abc,这三行代码用于控制第一个位置的字符,就是做用1
buf[start]=buf[i]; //好比,第一次时的第一个字符的全排列输出完后,
//而后把第一个和第二个字符进行交换,交换后,再排列
buf[i]=temp; //刚被交换到第一个位置字符的 全排列字符串
System.out.println("交换后:buf[start="+start+"]="+buf[start]);
System.out.println("交换后:buf[i="+i+"]="+buf[i]);
System.out.println("进入perm,start+1");
perm(buf,start+1,end);//经过start控制要被输出的字符串,对应做用2
temp=buf[start];//这三行是把变换后的字符串顺序进行还原,可以变换字符串顺序的,对应做用3
//代码就在递归函数perm()上面三行,用于交换字符串顺序来
buf[start]=buf[i]; //交换出所需的全排列字符串
buf[i]=temp;
System.out.println("变回来:buf[start="+start+"]="+buf[start]);
System.out.println("变回来:buf[i="+i+"]="+buf[i]);
}
}
}
}
输出:
交换前:buf[start=0]=a
交换前:buf[i=0]=a
交换后:buf[start=0]=a
交换后:buf[i=0]=a
进入perm,start+1
交换前:buf[start=1]=b
交换前:buf[i=1]=b
交换后:buf[start=1]=b
交换后:buf[i=1]=b
进入perm,start+1
abc
变回来:buf[start=1]=b
变回来:buf[i=1]=b
交换前:buf[start=1]=b
交换前:buf[i=2]=c
交换后:buf[start=1]=c
交换后:buf[i=2]=b
进入perm,start+1
acb
变回来:buf[start=1]=b
变回来:buf[i=2]=c
变回来:buf[start=0]=a
变回来:buf[i=0]=a
交换前:buf[start=0]=a
交换前:buf[i=1]=b
交换后:buf[start=0]=b
交换后:buf[i=1]=a
进入perm,start+1
交换前:buf[start=1]=a
交换前:buf[i=1]=a
交换后:buf[start=1]=a
交换后:buf[i=1]=a
进入perm,start+1
bac
变回来:buf[start=1]=a
变回来:buf[i=1]=a
交换前:buf[start=1]=a
交换前:buf[i=2]=c
交换后:buf[start=1]=c
交换后:buf[i=2]=a
进入perm,start+1
bca
变回来:buf[start=1]=a
变回来:buf[i=2]=c
变回来:buf[start=0]=a
变回来:buf[i=1]=b
交换前:buf[start=0]=a
交换前:buf[i=2]=c
交换后:buf[start=0]=c
交换后:buf[i=2]=a
进入perm,start+1
交换前:buf[start=1]=b
交换前:buf[i=1]=b
交换后:buf[start=1]=b
交换后:buf[i=1]=b
进入perm,start+1
cba
变回来:buf[start=1]=b
变回来:buf[i=1]=b
交换前:buf[start=1]=b
交换前:buf[i=2]=a
交换后:buf[start=1]=a
交换后:buf[i=2]=b
进入perm,start+1
cab
变回来:buf[start=1]=b
变回来:buf[i=2]=a
变回来:buf[start=0]=a
变回来:buf[i=2]=c
拓展1:
若是不是求字符的全部排列,而是求字符的全部组合,应该怎么办?仍是输入三个字符a、b、c,则它们的组合有a、b、c、ab、ac、bc、abc。当交换字符串中两个字符时,虽然能获得两个不一样的排列,但倒是同一个组合。好比ab和ba是不一样的排列,但只算一个组合。
解题思路:在求一个字符串中全部字符的组合的时候,针对一个字符,有两种状况,假设在长度为n的字符串中选择长度为m的组合字符串,
第一是选择长度为n的字符串中的第一个字符,那么要在其他的长度n-1的字符串中选择m-1个字符
第二是不选择长度为n的字符串中的第一个字符,那么要在其他的长度n-1的字符串中选择m个字符
递归结束的条件就是,当m为0,即从字符串中再也不选出字符的时候,这个时候已经找到了m个字符的组合,输出便可。还有一个条件是,当输入的字符串是串,天然是不能从中选出任何字符的。
package cglib;
import java.util.ArrayList;
import java.util.List;
public class DeleteNode{
public static void main(String ss[]) {
perm("123");
System.out.println();
}
// 求字符串中全部字符的组合abc>a,b,c,ab,ac,bc,abc
public static void perm(String s) {
List<String> result = new ArrayList<String>();
for (int i = 1; i <= s.length(); i++) {
perm(s, i, result);
}
}
// 从字符串s中选择m个字符
public static void perm(String s, int m, List<String> result) {
// 若是m==0,则递归结束。输出当前结果
if (m == 0) {
for (int i = 0; i < result.size(); i++) {
System.out.print(result.get(i));
}
System.out.println();
return;
}
if (s.length() != 0) {
// 选择当前元素
result.add(s.charAt(0) + "");
perm(s.substring(1, s.length()), m - 1, result);
result.remove(result.size() - 1);
// 不选当前元素
perm(s.substring(1, s.length()), m, result);
}
}
}
输出:
1
2
3
12
13
23
123
或者用二进制表示:
n个字符的全部组合个数就是2^n -1 个。既然咱们能够知道总数,那么不妨把这些数字换成二进制码,就有2^n -1组二进制码,细心观察这些0101的二进制码,咱们就会发现里面隐藏着一个规律,那就假如咱们把那组二进制跟咱们的字符串联想在一块儿,而后把出现1的 位置的字符连起来,不就是对应其中一种组合状况吗?换种说法,那2^n -1个数所对应的二进制码,就是咱们要的组合啊。那么接下来咱们只须要把里面1的索引位置找出来,把字符串里面对应索引的字符取出来拼在一块儿就好了~~ 固然我以为这里还有优化的余地,例如如何更快地找出1所在的位置之类~~
还有,假如目标字符串是26个字母,这种算法列出组合,比网上的那些递归方法快差很少一倍(前提条件是不记录结果,或者直接输出结果,由于记录全部结果须要大量内存,不加内存会爆的........
package cglib;
public class DeleteNode{
public static void main(String[] args) {
String temp = "abc";
String[] results = getResult(temp);
for (int i = 0; i < results.length; i++) {
System.out.println(results[i]);
}
}
private static String[] getResult(String str){
int start = 1;
int strSize = str.length();
double max = Math.pow(2, strSize-1);
double total = Math.pow(2, strSize)-1;
String[] result = new String[(int)total];
int step;
int index;
StringBuilder sb;
while (start <= total) {
sb = new StringBuilder();
step = 1;
index = 0;
while (step <= max) {
int temp = step & start;
if (temp != 0) {
sb.append(str.charAt(index));
}
step <<= 1;
index++;
}
result[start -1] = sb.toString();
start++;
}
return result;
}
}
输出:
a
b
ab
c
ac
bc
abc
拓展2:
当输入一个含有8个数字的数组,判断有没有可能把这8个数字分别放到正方体的8个顶点上,使得正方体上三组相对的面上的4个顶点的和都相等。
能够先求出a1-a8这8个数字的全部排列,而后判断有没有某一个的排列符合题目设定的条件,即a1+a2+a3+a4 = a5+a6+a7+a8,且a1+a3+a5+a7 = a2+a4+a6+a8,且a1+a2+a5+a6=a3=a4+a7+a8。求8个数字的排列和“面试题28:字符串的排列”中求字符串的排列相似,可 以将求8个数字的排列的问题分解下,将8个数字中的1个轮流固定放在数组的第一个位置,而后求剩下7个数字的排列,再依次递归下去。
至关于求出8个数字的全排列,判断有没有一个排列符合题目给定的条件,即三组对面上顶点的和相等。
package cglib;
public class DeleteNode{
public static void main(String[] args) {
int A[] = {1,2,3,1,2,3,2,2};
int B[] = {1,2,3,1,8,3,2,2};
if(perm(A,0,A.length-1))
System.out.println("Yes\n");
else
System.out.println("No\n");
if(perm(B,0,B.length-1))
System.out.println("Yes\n");
else
System.out.println("No\n");
}
public static boolean perm(int[] buf,int start,int end){
if(buf==null || end!=buf.length-1)
return false;
boolean result = false;
if(start==end){
if(buf[0]+buf[1]+buf[2]+buf[3]==buf[4]+buf[5]+buf[6]+buf[7] &&
buf[0]+buf[2]+buf[4]+buf[6]==buf[1]+buf[5]+buf[3]+buf[7] &&
buf[0]+buf[1]+buf[4]+buf[5]==buf[2]+buf[3]+buf[6]+buf[7])
{
int i;
for(i=0;i<=end;i++)
{System.out.print(buf[i]); }
System.out.println();
result = true;
}
}
else{
for(int i=start;i<=end;i++){
//System.out.println("交换前:buf[start="+start+"]="+buf[start]);
//System.out.println("交换前:buf[i="+i+"]="+buf[i]);
int temp=buf[start];
buf[start]=buf[i];
buf[i]=temp;
result= perm(buf,start+1,end);
if(result)
break;
temp=buf[start];
buf[start]=buf[i];
buf[i]=temp;
//System.out.println("变回来:buf[start="+start+"]="+buf[start]);
//System.out.println("变回来:buf[i="+i+"]="+buf[i]);
}
}
return result;
}
}
输出:
12323212
Yes
No