动态规划及相关例题

动态规划及相关例题

一、定义

动态规划算法是通过拆分问题,定义问题状态和状态之间的关系,使得问题能够以递推(或者说分治)的方式去解决。动态规划与分治法类似,也是将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。在求解任一子问题时,列出各种可能的局部解,通过决策保留哪些有可能达到最优的局部解,丢弃其他的局部解。一次解决各子问题,最后一个子问题就是初始问题的解。

二、动态规划适用情况

如果要求一个问题的最优解(通常是最大值或者最小值),而且该问题能够分解成若干个子问题,并且小问题之间也存在重叠的子问题,则考虑采用动态规划。

三、使用动态规划具体步骤

我们把下面成为动态规划五部曲:
判断提议是否

  1. 判断题意是否为找出一个问题的最优解;
  2. 从上往下分析问题,大问题可以分解为子问题,子问题中还有更小的子问题;
  3. 从下往上分析问题,找出这些问题之间的关联(状态转移方程);
  4. 讨论底层的边界问题;
  5. 解决问题(通常使用数组进行迭代求出最优解)。

四、相关例题分析

前面说的迷迷糊糊,通过举例应该就能亲切的感受到动态规划如何解决一个问题了:

问题1:剪绳子(剑指offer第二版,面试题14)

问题描述:给你一根长度为n的绳子,请把绳子剪成m段(m和n都是整数,n>1且m>1),每段绳子的长度记为k[0],k[1],k[2],…k[m],请问k[0]*k[1]k[2]…*k[m]可能的最大乘积是多少?
**例如:**当绳子长度为8时,需要剪成3段,使得最后得到的乘积最大,我们把它剪成长度分别为2,3,3,此时得到的最大乘积是18。
看完题目,我们使用上面提到的“动态规划五部曲”解决问题
1、判断题意是否为找出一个问题的最优解
看到字眼是“可能的最大乘积是多少”,判断是求最优解问题,可以用动态规划解决;
2、从上往下分析问题,大的问题可以分解为子问题,子问题中还有更小的 子问题
题目中举了例子:当绳子的长度为8时,我们把它剪成长度分别为2,3,3,此时得到的最大乘积是18;我们可以从这里开始突破,把长度为8的绳子的最大乘积分解为数个子问题,长度为8的子问题可以把它看成长度为1和7的绳子的和,或者长度为2和6的绳子的和,或者长度为3和5的绳子的和,依次推类。
3、从下往上分析问题,找出这些问题之间的关联(状态转移方程)
在第二步的时候,我们已经从上到下分析问题了,现在我们要从下往上分析问题了。首先当绳子长度为8时,定义该问题的解为f(8),则f(8)的值就是f(1)*f(7),f(2)*f(6),f(3)*f(5),f(4)*f(4)他们之中的最大值,即f(8)=Max{f(1)*f(7),f(2)*f(6),f(3)*f(5),f(4)*f(4)},则只要知道f(1)到f(7)的值就能求出f(8),同理,对于f(7),只要知道f(1)到f(6)的值就能求出f(7),对于f(6),只要知道f(1)到f(6)的值就能求出f(6),依次类推,我们只要知道前面几个边界的值,就能后一步步迭代出后续的结果。
根据以上分析,可以推导出的状态转移方程(或者叫迭代规律)为:f(n)=Max{f(n-i)*f(i)} (i={1,2,3,…,n/2})。
4、讨论底层的边界问题

底层的边界问题说的就是最小的前几个数值的f(n)的值,本题中就是f(0)、f(1)、f(2)、f(3)的值:
对于f(0),长度为0的绳子,没办法剪,没有意义;
对于f(1),长度为1的绳子,没办法剪,设为1;
对于f(2),长度为2的绳子,只有一种剪法,剪成两段长度为1的绳子,但剪后的乘积为1,比自身更小;如果不是求自身的值,要求乘积最大值的话就没必要剪;
对于f(3),长度为3的绳子,只有一种剪法,剪成两段长度为1和2的绳子,但剪后的乘积为2,比自身更小;如果不是求自身的值,要求乘积最大值的话也没必要剪。
5、解决问题
这一步就是编写代码了:

public static int cutting(int n){
	//长度小于或等于1没法剪
	if(n<=1){return 0;}
	//对于长度为2的绳子,只有一种剪法,剪成两段长度为1的绳子,剪后乘积为1
	if(n==2){return 1;}
	//对于长度为3的绳子,只有一种剪法,剪成两段分别为1和2的绳子,但剪后的乘积为2
	if(n==3){return 2;}
	int max=0;
	//数组勇于存储绳子乘积最大值
	int[] value=new int[n+1];
	value[0]=0;
	value[1]=1;
	//剪后的乘积为1,比自身更小;如果不是求自身的值,要求乘积最大的话,就没必要剪了
	value[2]=2;
	//剪后的乘积为2,比自身更小;如果不是求自身的值,要求乘积最大值的话也没必要剪了
	value[3]=3;
	//从f(4)开始迭代
	forint i=4;i<=n;i++{
		int max=0;
		for(int j=1;j<=i/2;j++){
			int val=value[j]*value[i-j];
			max=val>max?val:max;
		}
		value[i]=max;
	}
	max=value[n];
	return max;
}

以上就是该问题的答案,感觉应该说的比较清楚了。

问题2:跳台阶问题

**问题描述:**一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个n阶台阶总共有多少种跳法。
针对该问题,依旧使用“动态规划五部曲”进行分析:
1、判断题意是否为找出一个问题的最优解
这个确实有点模糊,但是涉及到“总共”,“最多”,“最大”,“最小”这样的字眼,一般可以认为是求最优解问题都没错。(自己的理解,欢迎批评指正)
2、从上往下分析问题,大的问题可以分解为子问题,子问题中还有更小的子问题
题目中没有给例子,我们可以自己举点例子。例如,跳上一个6级的台阶,有多少种跳法;由于青蛙最后一次可以跳两阶,也可以跳一阶,所以我们可以分成两个情况:
1、青蛙最后一次跳了两阶,问题变成了“跳上一个4级台阶,有多少种跳法”;
2、青蛙最后一次跳了一阶,问题变成了“跳上一个5级台阶,有多少种跳法”;
由上可得:f(6)=f(5)+f(4);
依次类推:f(4)=f(3)+f(2);
3、从下往上分析问题,找出这些问题之间的关联(状态转移方程)
跟上面的例题1相同,可以由f(1)逐渐迭代上去,由步骤2可得,状态转移方程为:f(n)=f(n-1)+f(n-2);
4、边界情况分析
跳一阶时,只有一种跳法,所以f(1)=1;
跳两阶时,有两种跳法,直接跳2阶,或者两次跳1阶,所以f(2)=2;
跳两阶以上可以分解成上面的情况。
5、解决问题
到这一步就可以编写代码了

public static int jump(int n) {
        //无意义的情况
        if(n <= 0){return 0;}
        if(n == 1){return 1;}
        if(n == 2){return 2;}
        //数组用于存储跳n阶的跳法数
        int[] value = new int[n + 1];
        value[0] = 0;
        value[1] = 1;
        value[2] = 2;
        for(int i = 3; i <= n; i++) {
            value[i] = value[i - 1] + value[i - 2];
        }
        return value[n];
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值