动态规划(DP)之排座位:从一道排列问题说起

从一道排座位问题谈起

问题描述
在一个班级中挑选N个学生排成一列座位(保证有足够多的男生与足够多的女生),要求座位序列中男生互不相邻,求解有多少种排列方式?(挑选男生与女生的数量与排列方式均为任意)

例如挑选三个学生,那么所有排列为: 女女女、女男女,男女女,女女男,男女男
(时间限制:1s 空间限制:65536kb)

输入
第一个数为学生总数N (0<N<30)
输出
只有一行,保证男生与男生不相邻,座位排列的所有情况数目的结果
输入样例
3
输出样例
5

问题分析

第一种思路:如果单纯从高中数学的角度看,这是一道关于排列组合的问题。不难知道,当男生人数为x 时,女生人数为N-x, 且各男生与各女生视为完全相同的个体,此题可以使用插空法解决。为了保证男生之间互不相邻,男生需要站在女生之间或两侧,共(N-x+1)个位置,从中选取x个,共有C(x, (N-x+1)))种,男生可以没有,最多可以比女生多一个。假设男生有x人,女生有y人,则有关系:
在这里插入图片描述
由此可得方案总数为:
在这里插入图片描述
于是该题的解决转移到组合数的求解问题上。但组合数的求解往往需要大量的计算,如果想要优化,不仅会使得问题变得复杂,也会增加计算量,随着N的增加,运行时间会迅速增长。因此不做进一步讨论。

第二种思路:本题是一道较为明显的动态规划问题。对于长度为N的队伍,可以以这样的方式排列:先考虑队伍最左面的同学,如果该同学为女生,则与之相邻的可以是男生或女生,因此接着排列剩余的N-1位同学,问题转移到长度为N-1的情况;如果第一位同学是男生,则第二位一定是女生,第三位又可以任意排,问题转移到长度为N-2的情况。再考虑基本情况,对于N=1,有两种情况,对于N=2,有三种情况。
假设长度为N的方案数为f(N),有

在这里插入图片描述
可以用递归函数实现。其代码如下:

int get(int n)
{
	if(n == 1) return 2;
	if(n == 2) return 3;
	return get(n - 1) + get(n - 2);
}

以N=4为例,其调用情况绘制一颗树其中f(1)被计算了8次。不难发现,随着N的增加,程序运行效率将大幅降低,不能在较短时间内结束。该算法时间复杂度为O(2 ^ N),空间复杂度为O(2 ^ N)
在这里插入图片描述
于是考虑记忆化。我们可以开一个数组a,用a[i]表示队伍人数为i时的方案数量。按照上述关系,得到一种较短的AC代码如下:

#include<stdio.h>
int main()
{
	int n;
	scanf("%d",&n);
	int a[35]={0,2,3};
	int i;
	for(i=3;i<=n;i++)
	a[i]=a[i-1]+a[i-2];
	printf("%d",a[n]);
 }

该算法时间复杂度为O(N),空间复杂度为O(N)
至此,本题得以解决。与本题类似的还有经典的台阶问题:走完N级台阶,每次可以走一步或两步,一共有多少种走法。
同样利用f(x)=f(x-1)+f(x-2)的递推关系,只需考虑N=1和N=2的情况。
其变式也有很多,比如铺地砖问题:
对于2N的地面,如果只有21一种砖,可以在地面左上角竖着铺一块,问题变成2*(N-1)的情况,如何横着铺,则必须在左下角也横着铺一块,问题变成2*(N-2)的情况。
这里还有进阶版的铺地砖,问题变成两种地砖,这里贴上来自另一位网友的链接:
从铺砖问题到排列组合算法的实现

当然台阶问题还存在变式,比如每次可以走最多k个台阶(1<k<N),这需要算法的进一步优化,留给读者思考。

第三种思路:根据前文的分析,可以发现方案数的排列是斐波那契数列的一部分,可以利用通项公式:
在这里插入图片描述
当然,需注意该数列的第一项是2,需要调整指数为n,代码如下:

int get(int n) 
{
    double sqrt5 = sqrt(5);
    double left = (1.0 + sqrt5)/2.0;
    double right = (1.0- sqrt5)/2.0;
    return (int)round(pow(left, n));
}

该算法时间复杂度为O(1),空间复杂度为O(1)。
但该函数对于较大的N存在精度不足的问题,有待读者进一步改进。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值