题目说明
以前有个电视节目,全国各地的小学生在这个节目里参加“30人31足”竞赛。
后来电视剧里也出现过这些小学生练习的场景,
并且全国大赛时小学生们表现出来的速度也曾引人注目。
下面探讨一下什么样的排列顺序在“30人31足”比赛里比较有利。
多个女生连续排列,体力上会处于劣势,所以原则是尽量不让女生相邻(男生可以连续排列)。
求:30 个人排成一排时,一共有多少种有利的排列方式?
假设这里只考虑男女的排列情况,不考虑具体某个人的位置。本题和31足没啥关系,仅为了引出"不让女生相邻"。
所以题目转化为:30个人排成一排,女生不相邻,有多少种站位方式?
思路1
1.30个人,一个一个增加。如果最右边是男生,则可加男or女;如果最右边是女生,则只可加男。
2.用List<Integer>表示这个队伍
3.用1和0分别表示男生和女生
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