从【暴力递归】到【动态规划】,刷新你的认知

前言

开局先唠点嗑,无此雅意可掠过。
相信学算法的伙伴们都知道动态规划这一词汇。
但是常常碰到动态规划的题目,简直就是一脸懵逼,毫无思路。
当然我也不例外,初识动态规划的时候,看着题解的状态转移方程,是如此的简单,但是自己就是想不到。每每这种时候,脑袋里都会浮现这一句代码:sb.append(我)。
直到我看到了左神的视频,突觉”茅厕顿开“。有很多小伙伴在看到状态转移方程的时候,会不禁感叹,究竟是何许人也能想到如此nb的状态转移方程。但其实初出茅庐时动态规划的转移方程并不是那么容易想出来的,可以通过一步一步转换得来。当然天生就在智商方面氪金的神人便可另当别论。不过我们可以通过量变引起质变呀,通过一些题目来不断提高水平,慢慢的这类题目便可玩弄于指掌。

好戏开场

接下来用最经典的动态规划----01背包来,来展开从暴力递归到动态规划的神奇演变。
先上题为敬:

有n件物品和一个容量为bag的背包。
第i件物品的体积是c[i],价值是w[i],0<=i<N。求解将哪些物品装入背包可使价值总和最大。

上来就要你写出动态规划的状态转移方程,估计毫无头绪。那我们可以先想想怎么暴力的去解决这个问题。

暴力递归

思路:
对于每一件物品,我们都有两种选择,要或不要。那么就以这种思路先将暴力递归的解法写出来。

	//为了代码更简洁,减少递归调用时书写的参数
	//这里的w数组和v数组作为成员变量(c/c++为全局变量)
	
	//决策第i件物品要还是不要,bag为当前背包容量
	//调用:baoli(0,bag):从第一个物品开始,最初的背包容量
    public static int baoli(int i, int bag) {
    
    	//边界条件:
    	//i==n说明没有物品可以选择了
    	//bag==0说明背包已经不能装东西了
        if (i == n || bag == 0) return 0;
        int yes = 0, no;
        
        // 要第i件物品,前提是能装得下
        if (bag - w[i] >= 0)
            yes = v[i] + baoli(i + 1, bag - w[i]);//当前物品的价值+背包剩余的容量所能产生的价值
        
        // 不要第i件物品
        no = baoli(i + 1, bag);//对第i+1件物品做决策
        
        // 当然则其较大者
        return Math.max(yes, no);
    }

暴力递归还是很简单的吧,非常贴近人类的思维。


观察暴力递归

接下来,观察暴力递归,会发现:

1) baoli(i,bag) 的值来源于v[i] + baoli(i + 1, bag - w[i])baoli(i + 1, bag)
2)当i==n或bag= =0的时候,baoli(i,bag)=0

有没有发现,这两个递归中的部分有点像动态规划中的预处理和状态转移方程呢。没错,就是这样。

在这里插入图片描述

所以,对于每个baoli(i,bag)所依赖的位置都是正下方和左下方的结果。
那么,结果就可以从下往上地推得出。至于每行从右往左还是从左往右,那就无所谓啦。
暴力递归的调用是baoli(0,bag),那么dp的结果就是dp[0][bag]

这么一来,预处理、状态转移方程和结果的位置不就出来了嘛。
接下来看代码

    public static int dp(int n, int bag, int[] w, int[] v) {
        int[][] dp = new int[n + 1][bag + 1];
        //因为java创建的数组,所有元素的默认值都是0,所以预处理就可以免了
        for (int i = n - 1; i >= 0; --i) {
            for (int j = bag; j >= 0; --j) {
                dp[i][j] = Math.max(
                					j - w[i] >= 0 ? v[i] + dp[i + 1][j - w[i]] : 0
                					dp[i + 1][j],
                					);
            }
        }
        return dp[0][bag];
    }

根据暴力递归,直接就把动态规划给改出来了。是否觉得眼前一亮。
在这里插入图片描述
当想不到动态规划的状态转移方程的时候,不妨想想暴力递归,然后通过暴力递归直接改成动态规划。以免直接想状态转移方程这个极具抽象的一个玩意。
在经过量变到质变之后,相信你也可以直接想出状态转移方程,只是现在初出茅庐,欠缺经验而已。


最终版

其实上面的动态规划还可以优化:
因为dp[i][j]所依赖的只是下方和左下方的结果,所以用一维的dp数组,从右到左地推就可以了。

    public static int dp1(int n, int bag, int[] w, int[] v) {
        int[] dp = new int[bag + 1];
        for (int i = n - 1; i >= 0; --i) {
            for (int j = bag; j - w[i] >= 0; --j) {
                dp[j] = Math.max(dp[j], v[i] + dp[j - w[i]]);
            }
        }
        return dp[bag];
    }

每当动态规划没思路的时候,都可以尝试一下先想想暴力递归,尝试用暴力递归来引出状态转移方程。

最后附上左神的视频链接,视频中讲的更加细:暴力递归 --> 记忆化搜索 --> 动态规划
https://www.bilibili.com/video/BV1Ef4y1T7Qi?p=14&vd_source=454e419e241380a376eb0730483f6705

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值