面試題:對1、2、2、3、4、5六個數字進行排列組合

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、先進行排列,算出所有的排列個數,A66
2、排列個數sum除2,因爲有兩個重複的2(下面計算的個數時,也需要去重),A662
3、減去第一個要求的個數。第三位數固定爲4,剩下5個數排列,A552
4、再減去第二個要求的個數。3和5組合成35,與剩下的4個數排列。也可以組合成53。2A552
5、再加上同時滿足第一個和第二個要求的個數,因爲第3、4步已經重複計算的。第三位固定爲4,3和5組合,與剩下的進行排列,35的位置只有三個地方:354xxx,xx435x,xx4x35。組合成53也一樣。2(3A332)

綜上,排列總個數sum=A662A5522A552+2(3A332) = 360 - 60 - 120 + 18 = 198

算出來的結果還是相同的,看來沒錯

================================
晚上,一直在想,這遞歸方法裏面的變量太多了,肯定會導致內存消耗偏大。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。這麼看來,效率也提高了

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章