用空间换取时间——浅谈动态规划

灵感

递归能够解决的问题,说白了就是能分解为许多个相同类型的规模更小的子问题的问题,其实就是一种分治思想,递归只是其中的一种实现手段而已。而当存在着大量重复子问题的时候,单纯的递归效率确实太低了,所以这种情况下,若仍想使用递归,就必须使用带备忘录的递归。那么,是否存在一种非递归,也就是迭代的实现方法呢?
没错,它就是我们今天的主角——动态规划。

初识

我们先来回顾一下带备忘录的递归——将每次计算的结果储存起来,下次若再次遇到,则只需直接从记忆表中提取即可。这是一种用空间换取时间的方法,显然,这是十分正确有效的方法,毕竟,空间不足尚可弥补,时间不足那就真的没办法了。
不难发现,既然要将结果储存在一张表内,那么是不是可以采用迭代的方式直接将这张表的元素全部算出来呢?答案显然是肯定的,而这样直接利用迭代的方式将整张表全部算出来的方式,就是动态规划。

实现

仍然利用斐波那契数列的例子,动态规划的实现如下:

int F(int n) {
	vector<int> dp(n + 1, 1);	//创建dp数组用来储存计算结果
	for (int i = 3; i <= n; ++i) {	//从3一直算到n,并储存在表内
		dp[i] = dp[i - 1] + dp[i - 2];	//根据递推关系(在动态规划中称为状态转移方程)计算表中当前值
	}
	return dp[n];	//从表中取得所需要的值并返回
}

一般来说实现动态规划之前需要如下准备:

1.确认状态
2.确认状态转移方程

什么是状态?
状态就是指dp[n]意味着什么,比如这里的dp[n]就意味着斐波那契数列中的第n项。
什么是状态转移方程?
与递推关系式类似,就是指如何用之前表中计算得到的状态来得到当前状态(状态转移)的关系式。
如这里就是dp[n] = dp[n - 1] + dp[n - 2];

深入

我们之前说过,动态规划是用空间来换取时间,但事实上最终真正用到的往往只是表中的某个状态而已(往往是尾部),而且,表中所有状态最终都是由最基本的两个状态决定的。那么,是否能压缩空间,让空间复杂度也进一步缩小呢?
一般情况下,可以采用以下这种方式:

int F(int n) {
	int pre1 = 1, pre2 = 1, cur;	//首先用pre1和pre2记录所有基态,并用cur记录当前状态
	for (int i = 3; i <= n; ++i) {
		cur = pre1 + pre2;	//利用状态转移方程与之前计算得到的状态算出当前状态
		pre1 = pre2;	//更新状态pre1,这里pre1是cur前距离cur为2的状态
		pre2 = cur;	//更新状态pre2,这里pre2是cur前一个状态
	}
	return n < 3 ? 1 : cur;	//返回当前状态,也就是最终状态,但若所求状态即为基态,则可直接返回基态的值即pre1或pre2,当然本题也可以取巧,设cur初值为1,直接返回cur
}

显然,空间复杂度由原来的O(n)变为了O(1)。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值