动态规划

本文介绍了动态规划的基本概念,通过斐波那契数列和拆分问题实例,展示了如何利用递推关系避免重复计算,降低时间复杂度。重点讲解了动态规划的三个核心条件:最优性、无后效性和重叠子问题,并提供了一个求解最大值拆分方案的动态规划解决方案。最后,还讨论了空间优化的滚动数组技术。
摘要由CSDN通过智能技术生成

什么是动态规划

我们首先看一下递归求斐波那契数列的函数:

int fib(int n){
    if(n==1||n==2)
        return 1;
    else
        return fib(n-1)+fib(n-2);
}

假设我们输入的参数是5,那么求解过程为:

会发现其中有重复的计算,如fib(3)就算了两次,如果我们将规模扩大,那么重复的计算会更多。

为了避免重复计算,我们可以创建一个全局数组,存储fib(n)的值,当需要fib(n)时,可直接参与运算,而不需要再次递归。

#define Max 20
int dp[Max]={0};
int fib(int n){
    dp[1]=dp[2]=1;
    for(int i=3;i<=n;i++)
        dp[i]=dp[i-1]+dp[i-2];
    return dp[n];
}

这样就使时间复杂度从指数级降为了O(n),大大提高了算法效率。
这就是经典的动态规划算法的应用,而这个全局数组就充当了一个记事本。

动态规划的适用问题

动态规划的核心思想就是把多阶段的过程转化为很多单阶段过程,利用各阶段之间的关系求解。

动态规划算法的使用要包含3个条件:

(1)最优性原理:指问题的最优解所包含的子问题的解也是最优的,这样便可通过最优子问题的解最终求出问题的最优解。

(2)无后效性:一个确定了的状态不受后面状态的影响,比如一旦求出fib(3)=2,它就一直等于2,与之后的计算无关。

(3)有重叠子问题:即子问题之间有联系,比如斐波那契数列中新的一项等于前两项之和。

例题

求将正整数n无序拆分成最大数为k的拆分方案个数,要求所有的拆分方案不重复。

分析:首先我们看能否将这个问题转化为多个单阶段过程,题目要求最大值为k,那么我们可以将拆分分为两种:一种包含k;另一种不包含k。对于包含k的方案,可以理解为一个k和后面剩余的数,而后面的数中还有可能再次出现k,就相当于在把后面和为n-k的数进行拆分,最大值为k;假设存在一个函数f(n,k)能表示题目要求的拆分次数,那么包含k的方案就有f(n-k,k)种。同样,对于不包含k的方案,既然不包含他的最大值,我们就可以直接将其最大值改为k-1,即f(n,k-1);这样,我们就找到了当前状态与前面状态的关系:f(n,k)=f(n-k,k)+f(n,k-1),很显然它满足我们上面所说的3个条件,那么就可以使用动态规划求解。

确定了可以使用动态规划之后,最重要的一步就是找到状态方程,也就是前后子问题之间的关系。在上面推出的f(n,k)=f(n-k)+f(n,k-1)只是我们默认n>k且n,k均不唯一的情况。完整考虑得出的状态方程如下:

f(n,k)=

1 n=1 or k=1
f(n,n) n<k
f(n,n-1)+1 n=k
f(n-k,k)+f(n,k-1) n>k

代码如下:

#define Max 20
int dp[Max][Max]={0};
int solve(int n,int k){
	for(int i=1;i<=n;i++)
		for(int j=1;j<=k;j++){
			if(i==1||j==1)
				dp[i][j]=1;
			if(i<j)
				dp[i][j]=dp[i][i];
			else if(i==j)
				dp[i][j]=dp[i][i-1]+1;
			else
				dp[i][j]=dp[i-j][j]+dp[i][j-1];
		}
	return dp[n][k];
}

此题也可以使用递归解决,读者可自行尝试,比较体会动态规划算法的优越性。

补充

动态规划在一些情况下可以进行空间上的优化。比如最初的求斐波那契数列的问题,我们可以发现,实际参与计算就是三个量:fib(n),fib(n-1)和fib(n-2),但是我们却使用了长度至少为n的数组。实际上我们只需要定义一个长度为3的数组,通过数组的循环滚动使用来完成计算,我们称其为滚动数组

代码如下:

int dp[3]={0};
int fib(int n){
	dp[1]=dp[2]=1;
	for(int i=3;i<=n;i++)
		dp[i%3]=dp[(i-1)%3]+dp[(i-2)%3];
	return dp[n%3];
}
dp[1]=dp[2]=1;
	for(int i=3;i<=n;i++)
		dp[i%3]=dp[(i-1)%3]+dp[(i-2)%3];
	return dp[n%3];
}

这样,算法的空间复杂度就由O(n)变成了O(1)。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值