剑指Offer第28题
题目
输入一个字符串,打印出该字符串中字符的全部排列,比如输入abc。则打印出由字符abc,acb,bac,bca,cab,cba。
思路:
我们能够把复杂的问题拆分成小问题:
首先:确定第一个字符a,然后剩下后面两个bc,bc在进行排列
递归深入:首先确定第一个字符b,然后剩下的c进行排列
递归深入:c是最后一个字符。输出。
递归返回:交换bc,再次递归........
依次这样下去,当返回到第一个字符时,又再次把a和b一交换就可以。
代码例如以下:
<span style="white-space:pre"> </span>public static void Permutation(String str) { if(str == null || str.length()==0 )return ; char[] arr = str.toCharArray(); truePermutation(arr,0); } private static void truePermutation(char[] arr,int i) { if(i == arr.length - 1) { System.out.println(arr); } for(int j=i;j<arr.length;j++) { //交换好第一个和后面的, char temp = arr[j]; arr[j] = arr[i]; arr[i] = temp; //直接递归后面的就能够 truePermutation(arr,i+1); //交换回来,到第一次还没交换的时候 temp = arr[j]; arr[j] = arr[i]; arr[i] = temp; } }
--------------------------------------------------------------------------------------------------------------------------------------------------------------------
延伸1:
关于这道题能够延伸,上面的是没有出现反复字符。那假设出现反复字符会怎么样?上面的肯定不会合适的。
这时候。我们仅仅须要在处理前加一个推断,我们推断最開始的那一个字符和当前字符之间有没有反复的字符。
也就是说:当输入是 aac 的时候。我们推断第一个a和第二个a中间有相等的(把第二个a看成中间的),就不交换了。
所以我们每次交换的是字符1 和第一个不反复的字符2。
代码例如以下:
/** * * 思路: * 假设i与j之间。还有与j同样的,那就不交换了,说明有反复值 * 仅仅须要将i与反复值的第一个交换就可以 * * 去重的全排列就是从第一个数字起每一个数分别与它后面非反复出现的数字交换 * */ public static void sequenceRepeatString(String str) { if(str ==null || str.length()==0)return ; char [] arr = str.toCharArray(); truesequenceRepeatString(arr,0); } private static void truesequenceRepeatString(char[] arr,int i) { if(i == arr.length - 1) { System.out.println(arr); } for(int j=i;j<arr.length;j++) { //在这里增加推断 if(isSwap(arr,i,j)) { //交换好第一个和后面的, char temp = arr[j]; arr[j] = arr[i]; arr[i] = temp; //直接递归后面的就能够 truesequenceRepeatString(arr,i+1); //交换回来,到第一次还没交换的时候 temp = arr[j]; arr[j] = arr[i]; arr[i] = temp; } } } private static boolean isSwap(char[] arr, int i, int j) { for(int k = i;k<j ;k++) { if(arr[k] == arr[j]) return false; } return true; }
课后题:
剑指offer的后面提到,假设要求一个字符串的组合,abc的组合就是:a,b,c,ab,ac,bc,abc。ab和ba是一个组合。
这也就是所谓的排列组合。
思路:
当我们要选择长度为n的字符时,每次碰到一个字符的时候,要么将这个字符放入到所要选的集合中。在后面的字符中选n-1个字符。
要么就不放入这个字符,在后面的字符中选n个字符。
代码例如以下:
/** * 字符串的全组合,字符串内无反复反复 * abc: * a,b,c,ab,ac,abc * 像ab,bc是一个组合 * * */ public static void combineString(String str) { if(str == null)return; //定义一个栈保存我们所要选的序列 Stack<Character> st = new Stack<Character>(); for(int i =1 ;i<=str.length();i++) trueCombineString(str.toCharArray(),i,0,st); } private static void trueCombineString(char[] arr,int m,int index, Stack<Character> st) { if(arr == null )return; if(index==arr.length || m==0) { if(m==0) //m不等于0,就说明后面的字符总长度加起来都不等于所要选择的长度。System.out.println(st.toString()); return ; } //假设选第一个 st.push(arr[index]); trueCombineString(arr,m-1,index+1, st); st.pop(); //不选第一个 trueCombineString(arr,m,index+1, st); }
在网上看到一段很好的解法:利用位运算。
也就是说:我们平时看到的二进制:001 010 011 100 101.....这些都是无反复的排列组合。能够利用这个特性,将abc相应的位置输出。
比方说001,就输出c。011。输出bc。111,输出abc。
/** * * 位运算求组合 * * * */ public static void bitCombine(String str) { for(int i=1;i<(1<<str.length());++i) //比方"abc",i就必须小于1000(二进制),这样后面三位才干够推断 trueBitCombine(str,i); } private static void trueBitCombine(String str, int i) { for(int k=0;k<str.length();k++) { //System.out.println((i & (1<<k))+"....."); if((i & (1<<k)) !=0) //一次推断i的那一位是1,是就输出 System.out.print(str.charAt(k)+" "); } System.out.println(); }
思路:
利用一个集合保存我们已经输出过的值。当再次输出的时候,推断集合中有没有该元素,假设有,就不用输出。
代码就不上了。