动态规划算法学习总结(带案例)

【动规算法学习总结】


首先,遇到动态规划问题要找到三个重要元素
1.最优子结构
2.边界
3.状态转移方程

【最优子结构】
通俗来说,就是具有规律性的结果的获取方式。
如上楼梯问题中, 上第10层的情况种类 = 上第8、9层的情况种类之和。第9层的结果又为第7、8层结果之和。
又如击鼓传花问题中。 传m次传给1的情况种类 = 传m-1次传给n、2的情况种类之和。 传m-1次传给2的情况又为传m-2次传给1、3情况之和。
(详细内容见另外两篇博客)

【边界】
任何问题都要有边界,否则我们就要无休止的循环下去了。
在得出最优子结构后,在将结果递归至某一处时(一般在数据集边缘),可以直接得出其结果。而这个就是边界。
如上楼梯问题(初始位置为第0阶楼梯),其边界为:上第1阶时,结果为1。 上第二阶时,结果为2({1,1},{2}})。
又如击鼓传花问题,传1次传给2的结果为1,传1次传给n的结果为1。

【状态转移方程】
在得出最优子结构后,就可以对应归纳出状态转移方程。
如上楼梯问题,F(n) = F(n-1) + F(n-2)
但是我个人觉得,如果时间紧迫(如笔试题),也可以放弃得到其最优子结构和最优的状态转移方程。 而是采用判断的形式,得出对应的结果。
如在击鼓传花问题当中,我将其分为三种情况:

 * dp[m][1]等于第m-1次传递到小赛左右两边的人的情况之和,即 dp[m][1] = dp[m-1][n] + dp[m-1][2]
 * i代表传递i次,j代表传递给第j个人,则
 * j == 1时:
 *   dp[i][j] = dp[i-1][n] + dp[i-1][j+1];
 * j == n时:
 *   dp[i][j] = dp[i-1][j-1] + dp[i-1][1];
 * 正常情况:
 *   dp[i][j] = dp[i-1][j-1] + dp[i-1][j+1];

而对应的代码,同样是判断

//dynamic planing
        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                if(j == 1){
                    dp[i][j] = dp[i-1][n] + dp[i-1][j+1];
                }else if(j == n){
                    dp[i][j] = dp[i-1][j-1] + dp[i-1][1];
                }else{
                    dp[i][j] = dp[i-1][j-1] + dp[i-1][j+1];
                }
            }
        }
        System.out.println(dp[m][1]);

其次,在得到三个元素后,需要将思路逆转过来
为什么说逆转过来呢?
因为刚才我们得到的最优子结构,是采用自顶向下的思路推理分析的。而我们所采用的动态规划解法,则自底而上,从我们找到的边界值开始,从底层,采用for循环,一步一步得到上方的值,最终得到我们最顶端的值。
举例说明:

【上楼梯问题】
知道1阶和2阶的结果,我们就可以得到3阶的结果。
知道2阶和3阶的结果,我们就可以得到4阶的结果。

最终,我们得到了10阶的结果。
具体代码如下:

static int step(int n){
        if (n == 1){return 0; }
        if (n == 2){return 1; }
        if (n == 3){return 2; }
        int a = 1, b = 2, step = 0;
        for (int i = 4; i <= n; i++){
            step = a + b;
            a = b;
            b = step;
        }
        return step;
    }

【击鼓传花问题】
已知dp[1][2]=1,dp[1][n]=1,其余dp[1][i]=0。
可以根据dp[1][2]=1,dp[1][n] = 1 ,得到dp[2][1] = 2;
根据dp[1][1]=0,dp[1][3] = 0,得到dp[2][2] = 0;
根据dp[1][2] = 1, dp[1][4] = 0,得到 dp[2][3] = 1;

最终得到dp[m][1]的结果。

以上就是自己学习动态规划的总结。


【补充内容】
其实,在得到【最优子结构】,【边界】,【状态转移方程】后最直接的解法是:
采用递归算法(时间复杂度高,空间复杂度高)
以及备忘录算法(时间复杂度低,空间复杂度较高)
最优的才是动态规划算法

简要介绍一下这里提到的两种算法:
【递归算法】
以上楼梯问题为例:

int step(int n){
	if(n == 0)
		return 0;
	if(n == 1)
	    return 1;
	if(n == 2)
	    return 2;
	return step(n-1) + step(n-2);
}

递归最为简单,直接把边界值,状态转移方程带入求解即可。
但是效率极低,因为其它重复计算了很多内容。(一旦n值极大,程序马上崩溃)。

【备忘录算法】
思路:采用递归算法,但是增加一个缓冲池,每次取值时,
判断缓冲池中是否有?取出:计算得出并放入缓冲池
伪代码如下:

Map<Integer,Integer> cache = new HashMap<>();
int step(int n){
	if(n == 0)
		return 0;
	if(n == 1)
	    return 1;
	if(n == 2)
	    return 2;
	if(cache.contains(n){
		return map.get(n)
	}else{
		int res = step(n-1) + step(n-2);
		cache.put(n,res);
		return res;
	}
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

喜鹊先生Richard

随缘~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值