动态规划算法

    动态规划英语Dynamic programming,DP)是一种在数学计算机科学经济学中使用的,通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。 动态规划常常适用于有重叠子问题最优子结构性质的问题,动态规划方法所耗时间往往远少于朴素解法(出自维基百科-动态规划

    本文主要参考了如下文章:

动态规划:从新手到专家

最长递增子序列 O(NlogN)算法

    

    使用动态规划算法求解问题的主要思想是先缩小求解问题的规模,然后逐步增加问题的规模进行求解,最后得出所要求问题的解。动态规划算法可以将问题的规模逐步缩小,如果所求解问题的规模问N,那么要求解问题N,则可以先求解规模问N-1的问题,然后再由规模为N-1的问题的解求得规模问N的问题的解(貌似很绕口)。下面以一个例子来说明:

如果我们有面值为1元、3元和5元的硬币若干枚,如何用最少的硬币凑够11元?

要凑够11元,那么如果我们知道凑够10元的所需的硬币的最少个数位2,则凑够11元所需要的硬币的最少个数为3。凑够10元所需的硬币最少个数为2又是如何求得的呢?进一步缩小规模,求得凑够9元所需的硬币个数位3,有式子:

f(10) = min{f(10-9) + 1(一个一元硬币), f(10-3) + 1(一个3元硬币), f(10-5) + 1(一个5元硬币)}

继续缩小问题的规模,直到f(1) = 1,即凑够1元需要一个硬币。咋一看,上面的算法是一个递归算法了,其实不然,上面只是我们的分析思路。动态规划算法就是将上面的思路反过来解决问题,即先求规模小的问题,再求规模大的问题:

1.先求出凑够1元所需最少硬币个数为1,f(1) = f(0)+1;

2.再求出凑够2元所需最少硬币个数为2,f(2) = f(1) + 1;

3.凑够3元所需硬币的个数为1,f(3) = min{f(3-1) + 1, f(3-3) + 1},此时可以使用3元硬币;

4.凑够4元所需硬币的个数位2,f(4) = min{f(4-3) + 1, f(4-1) + 1};

5.凑够5元所需硬币的个数位1,f(5) = min{f(5-3) + 1, f(5-1) + 1};

......

如此这样计算知直到凑够11元时。在上面的每一步计算中,我们都将所需的最小硬币个数保存下来,这样在后面的步骤中就可以直接使用而不需要重新计算。上面每一步所对应的硬币个数位:


根据上面的分析我们可以写出求解这个问题的代码:

#include <stdio.h>
#include <stdlib.h>

int dp(int sum, int v[], int len) {
	int i, j;
	/*min[i]表示凑够i元需要多少个硬币*/
	int *min = (int *)malloc(sizeof(int) * (sum + 1));
	min[0] = 0;
	for(i = 1; i <= sum; i++) {
		for(j = 0; j < len; j++) {
			min[i] = min[i-1] + 1;
			if(i >= v[j] && (min[i-v[j]] + 1) < min[i]){
				min[i] = min[i-v[j]] + 1;
			}	
		}
	}
	printf("%d", min[sum]);
}

int main() {
	int sum = 11;
	int a[] = {1, 3, 5};
	dp(11, a, 3);
}

下面再看一个使用动态规划算法解决的一个例子:

一个序列有N个数:A[1],A[2],…,A[N],求出最长非降子序列的长度,如序列2,1,5, 3, 6, 4, 8, 9, 7最长非降子序列长度就是5。

求解这个问题应该从序列第一个数开始遍历序列,然后记录下到每个元素为止,最长子序列的长度,定义变量i,表示序列元素的索引,定义数组整型d[]记录截止到该元素时,最长子序列的长度。从序列的第一个元素开始:

i = 0,此时A[0] = 2, d[0] = 1;

i = 1,此时A[1] = 1, d[1] = 1,因为A[1] < A[0];

i = 2,此时A[2] = 5, d[2] = 2,因为A[2] > A[1];

i = 3, 此时A[3] = 3, d[3] = 2,因为A[3] < A[2];

......

如此可以计算出截止到第i个元素的最长子序列长度。

根据以上思路,使用C语言实现代码如下:

#include<stdio.h>
#include<stdlib.h>

/*使用dp算法解lis问题*/
int lisDp(int a[], int len) {
	int i, j;
	int maxLen = 0;
	int *d = (int *)malloc(sizeof(int) * len);
	for(i = 0; i < len; i++) {
		d[i] = 1;
		for(j = 0; j < i; j++) {
			if(a[j] <= a[i] && d[j] + 1 > d[i]){
				d[i] = d[j] + 1;
			}
		}
		if(d[i] > maxLen) maxLen = d[i];
	}
	return maxLen;
}

int main() {
	int a[] = {2, 1, 5, 3, 6, 4, 8, 9, 7};
	printf("%d\n",lisDp(a, 9));
	return 0;
}
这个问题使用动态规划算法解决并不是最好的方式,它的时间复杂度为O(n2),更巧妙的算法时间复杂度为O(nlogn),见O(nlogn)算法


下面我们将使用动态规划算法解决几个问题:

面试题:

给定字符串,以及一个字典,判断字符串是否能够拆分为字段中的单词。例如,字段为{hello,world},字符串为hellohelloworld,则可以拆分为hello,hello,world,都是字典中的单词。



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值