程序员的算法趣题:Q17 挑战30人31足(Java版)

题目说明

以前有个电视节目,全国各地的小学生在这个节目里参加“30人31足”竞赛。
后来电视剧里也出现过这些小学生练习的场景,
并且全国大赛时小学生们表现出来的速度也曾引人注目。
下面探讨一下什么样的排列顺序在“30人31足”比赛里比较有利。
多个女生连续排列,体力上会处于劣势,所以原则是尽量不让女生相邻(男生可以连续排列)。
求:30 个人排成一排时,一共有多少种有利的排列方式?
假设这里只考虑男女的排列情况,不考虑具体某个人的位置。

本题和31足没啥关系,仅为了引出"不让女生相邻"。
所以题目转化为:30个人排成一排,女生不相邻,有多少种站位方式?

思路1

1.30个人,一个一个增加。如果最右边是男生,则可加男or女;如果最右边是女生,则只可加男。
2.用List<Integer>表示这个队伍
3.用10分别表示男生女生
4.用递归表示每一个位置的所有选择
5.用count统计最终结果

代码1

public static int count = 0; // 统计结果个数

public static void main(String[] args) {
    // 1.队伍 2.人数上限
    add(new LinkedList<Integer>(), 30); // 自定义的方法
    System.out.println("count = " + count);
}
/**
 * 在当前的队伍中加入一个人
 * @param queue     当前的队伍
 * @param maxLimit  最大人数限制
 */
private static void add(LinkedList<Integer> queue, int maxLimit) {
    // 不合法情况
    if (queue == null || queue.size() > maxLimit) return;
    
    // 成功情况
    if (queue.size() == maxLimit) {
        count ++; // 结果数量+1
        System.out.println(queue); // 查看队伍中的排列情况
        return;
    }

    // 如果一个人都没有 or 最后一个是男生
    if (queue.size() == 0 || queue.getLast() == 1) {
        // 加一个男生的情况
        queue.addLast(1);
        add(queue, maxLimit);
        queue.removeLast();

        // 加一个女生的情况
        queue.addLast(0);
        add(queue, maxLimit);
        queue.removeLast();
        
    } else { // 如果最后一个是女生
        // 只能加一个男生
        queue.addLast(1);
        add(queue, maxLimit);
        queue.removeLast();
    }
}

结果1

......省略很多条数据......
[0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0]
[0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1]
[0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1]
[0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0]
[0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1]
[0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0]
[0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1]
count = 2178309

思路2

上述代码涉及到集合的大量增删,时间效率很低。
如果不在乎具体的排列方式,只需要求得结果,则可以去掉集合

代码2

public static int count = 0; // 统计结果个数

public static void main(String[] args) {
    // 1.当前队伍中的人数 2.最后一个人的性别 3.人数上限
    add(1,1,30); // 第一个是男生的情况
    add(1,0,30); // 第一个是女生的情况
    System.out.println("count = " + count);
}
/**
 * 纯粹用于计算,不需要操作集合,耗时更短
 * @param num      当前的人数
 * @param last     最后一个人是男是女
 * @param maxLimit 总人数
 */
private static void add(int num, int last, int maxLimit) {
    // 不合法情况
    if(num <= 0) return ;
    if (last != 1 && last != 0 ) return;

    // 成功情况
    if (num == maxLimit) {
        count ++;
        return;
    }

    // 如果最后一个是男生
    if (last == 1) {
        // 尝试加一个男生
        add(num + 1, 1, maxLimit);

        // 尝试加一个女生
        add(num + 1, 0, maxLimit);
    } else { // 如果最后一个是女生
        // 只能加一个男生
        add(num + 1, 1, maxLimit);
    }
}

结果2

count = 2178309

思路3

假设目前该加入第n个人
    如果第n-1个人是男生,则现在可以加入男生or女生;
    如果第n-1个人是女生,则现在只可以加入男生。
换言之:
    如果第n个人是男生,则之前的站位方式是f(n-1)种;
    如果第n个人是女生,则之前的站位方式是f(n-2)种。因为她前一个人必定是男生
由此可得:f(n) = f(n-1) + f(n-2); // 斐波那契数列的公式

代码3

public static Map<Integer,Integer> hmap = new HashMap<>(); // 内存化提升时间性能,避免重复计算
public static int count = 0; // 统计结果个数
public static void main(String[] args) {
    System.out.println("count = " + f(30));
}
/**
 * n个人站成一排的站法有多少种
 * @param n 当前人数
 * @return  有几种站法
 */
private static int f(int n){
    if(n < 0) reutrn 0;  // 不合法情况

    if(n == 0) return 1; // 0个人的站位只有一种:没有人
    if(n == 1) return 2; // 1个人的站位有两种:男 or 女

    if(! hmap.containsKey(n)){ // 如果没有,则存入
        hmap.put(n, f(n-1) + f(n-2)); // 当前位置站男生的方式 + 当前位置站女生的方法
    }

    return hmap.get(n);
}

结果3

count = 2178309

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值