面试题:对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。这么看来,效率也提高了

  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值