Sorry,到現在還沒完全進入寫代碼的狀態,頭暈暈的。。。
上午看到一同學發了一道排列組合的編程筆試題,感覺挺有意思的,反正都沒進入狀態,就先試下,看能否解決,並進入狀態。
題目:用1、2、2、3、4、5這六個數字,用Java寫一個main函數,打印出所有不同的排列,如512234、412345等,要求:4不能在第三位,3與5不能相連。
題目中的排列412345,有兩個4,應該是有問題的。知道就行,不管了。
思路:
一開始的想法就是:實現第一個要求,從第一個位置開始排列,到第三個位置,把4剔除掉,即跳過。同樣第二要求也是這樣,放入3或5時,判斷前面是否有5或3,有則跳過。說實話,有點暈了。
後來想到高中的求排列組合的個數,這樣的解法應該就是總數減去符合要求的個數。全部排列出來,放在集合中,再從集合中查找是否有滿足條件的元素,有就剔除。
OK,就按後面的方法來。
使用Android Studio來創建Java工程:在已有的Project工程中新建Module,選Java Library,運行時選中.java文件,右鍵Run ‘JavaTest.main()’
上代碼,裏面註釋的還是比較詳細
package com.test;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.TreeSet;
public class JavaTest {
static final char[] CHARS = {'1', '2', '2', '3', '4', '5'};
// 裏面有兩個2,爲了排除相同的組合,使用二叉樹TreeSet集合存儲(使用默認的自然順序排序即可)
static TreeSet<Integer> mResultSet = new TreeSet<>();
public static void main(String[] args){
ArrayList<Character> charList = new ArrayList<>();
for (int i=0; i< CHARS.length; i++) {
charList.add(CHARS[i]);
}
// 1、獲取所有的排列組合
combination(charList, new StringBuilder());
print("all combination = " + mResultSet.size());
for (int r : mResultSet) {
print(r);
}
// 2、去掉不符合要求的
for (Iterator<Integer> it = mResultSet.iterator(); it.hasNext();) {
int r = it.next();
String rs = String.valueOf(r);
if ("4".equals(String.valueOf(rs.charAt(2))) || rs.contains("35") || rs.contains("53")) {
it.remove();
}
}
print("the result = " + mResultSet.size());
for (int r : mResultSet) {
print(r);
}
}
/**
* 根據給定的字符數組進行組合。使用遞歸方法
* @param chars 需要排列的字符數組
* @param befores 之前排列好的字符串容器
*/
public static void combination(ArrayList<Character> chars, StringBuilder befores){
// 設定跳出遞歸的條件
if (chars == null || chars.size() == 0){
return ;
}
if (chars.size() == 1) {
befores.append(chars.get(0));
mResultSet.add(Integer.parseInt(befores.toString()));
return ;
}
for (int i=0; i<chars.size(); i++) {
ArrayList<Character> newCharList = new ArrayList<>(chars);
newCharList.remove(i);
StringBuilder sb = new StringBuilder(befores);
sb.append(chars.get(i));
combination(newCharList, sb);
}
}
/**
* 打印
* @param object
*/
public static void print(Object object) {
System.out.println(object);
}
}
…
看到這樣的題目,第一感覺就是回到了高中,做排列組合題目。當時是計算這樣的排列總個數,不完全記得,但大概應該是這樣:
1、先進行排列,算出所有的排列個數,
2、排列個數sum除2,因爲有兩個重複的2(下面計算的個數時,也需要去重),
3、減去第一個要求的個數。第三位數固定爲4,剩下5個數排列,
4、再減去第二個要求的個數。3和5組合成35,與剩下的4個數排列。也可以組合成53。
5、再加上同時滿足第一個和第二個要求的個數,因爲第3、4步已經重複計算的。第三位固定爲4,3和5組合,與剩下的進行排列,35的位置只有三個地方:354xxx,xx435x,xx4x35。組合成53也一樣。
綜上,排列總個數sum=
算出來的結果還是相同的,看來沒錯
================================
晚上,一直在想,這遞歸方法裏面的變量太多了,肯定會導致內存消耗偏大。CSDN裏找到一篇一樣的博客:http://blog.csdn.net/zhutulang/article/details/7775644
我的思路和這個大神的幾乎一樣,但在遞歸時他使用了一個StringBuilder,內部也沒有新建變量。
因此,我也做了改進:
1、使用一個ArrayList字符集合,在遞歸前刪除當前的字符,遞歸完後又添加到原位置。(但要注意併發修改異常,這裏沒使用迭代器,直接用索引)
2、使用一個StringBuilder,在遞歸前添加當前的字符到末尾,遞歸完後把末尾的字符刪除。
遞歸方法,修改如下:
/**
* 根據給定的字符數組進行組合。使用遞歸方法
* @param chars 需要排列的字符數組
* @param befores 之前排列好的字符串容器
*/
public static void combination2(ArrayList<Character> chars, StringBuilder befores){
// 設定跳出遞歸的條件
if (chars == null || chars.size() == 0){
return ;
}
if (chars.size() == 1) {
befores.append(chars.get(0));
mResultSet.add(Integer.parseInt(befores.toString()));
befores.deleteCharAt(befores.length()-1);
return ;
}
// 不能使用iterator或foreach(內部也是使用的iterator),否則會造成ArrayList的成員變量modCount變化,從而導致併發修改異常
for (int i=0; i<chars.size(); i++) {
Character c = chars.get(i);
chars.remove(i);
befores.append(c);
combination2(chars, befores);
chars.add(i, c);
befores.deleteCharAt(befores.length()-1);
}
}
結果是一樣的,但這樣肯定更加節省內存空間。另外測了下兩個遞歸方法的時間:前面方法平均耗時8ms,後面改進的平均耗時6ms。這麼看來,效率也提高了