动态规划入门

366 阅读3分钟

01背猫问题

法外狂徒小谢,准备去偷隔壁小姐姐家的三只猫。三只猫重量不同价值不同。但贫穷的小谢只有一只不是非常牢固容量有限的猫包。那么他该如何选取,使得偷盗的价值最高嘞?🤭

我的解题思路

  1. 明确方向,偷一次不容易,肯定要偷的回本,所以要偷价值最高的东西。但是能力有限,包只有这么大,因此要尽可能的利用好每一寸土地。

  2. N = 3 小姐姐家的三只猫

    wt = [2,1,3] 每只猫的重量

    v = [4,2,3] 每只猫的价值

    B = 4 猫包的最大承受重量

    开一个二位数组,来解最优偷法,f(k,w) (k = 可选猫的数量, w = 猫包还可以装几斤的猫)

    第一次选择:可选3只猫,猫包容量为4 f(3,4)。如果选择了1号猫(质量为2,价值为4),那么包的最终价值Bv = f(3-1,4-2) + 4。继续分解f(2,2)。如果我不选1号,那么我的f(2-1,4)。

    第二次选择:可选2只猫,猫包容量为2 f(2,2)。如果选贼了2号猫猫头,猫包的最终价值Bv = f(1,1) + 2 + 4。如果我不选2号猫猫,那么我的f(2-1,2).

    第三次选择:可选一只猫,猫包容量为 f(1,1)。此时发现第三支猫重3,要不起,那么f(1,1) = 0 Bv = 0 + 2 + 4 = 6

    问题来了❓
    Bv = 6 是偷的最划算的吗?解析以下步骤: image.png

有没有什么规律呢?🎈

会发现有两个问题,一个是选不选,一个是拿不拿的了(背包容量够吗) 我们的目标是要拿最多的东西,所以在选择时就要拿最大的。可以总结出以下方程:

f(k,w):k件物品,w包的容量,f(k,w)能偷到的最大价值。

w(k)>w(太重放不下)f(k,w)=f(k1,w) w(k)>w (太重放不下) f(k,w) = f(k-1,w)

w(k)<w(可以放下)f(k,w)=maxf(k1,w),f(k1,ww(k)+v(k)) w(k)<w (可以放下) f(k,w) = max{f(k-1,w),f(k-1,w-w(k)+v(k))}

总结容量不同的情况

001234
000000
100444
202466
302466

我们要的结果在表[3][4]位置

代码

var maxCat = function(w,v,b) {
    totalCat = w.length;
    let dp = new Array(totalCat+1).fill(0).map(item => new Array(b+1).fill(0))
    for(i = 0;i<=totalCat;i++){
        for(j = 0; j<=b ;j++){
            if(i=== 0 || j===0 ){
                dp[i][j] = 0
            }else{
                if(w[i-1]>j){ //第i件装不下
                    dp[i][j] = dp[i-1][j]
                }else{
                    dp[i][j] = Math.max(dp[i-1][j],dp[i-1][j-w[i-1]]+v[i-1])
                }
            }
        }
    }
    return dp[totalCat][b];
};

多少种XX问题

多少条路径从A--->B

image.png

我的解题思路

  1. 明确方向:我们的目的是从左上角走到右下角,并且一次只能向下或者向右移动一步

  2. 观察特殊,我们分析可得,第一行和第一列的值是固定的,也就是前一项+1(上一项+1)

  3. 列表 | start(1) | 1 | 2 | 3 | 4 | 5 | 6 | n-1 | | --- | --- | --- | --- | --- | --- | --- | --- | | 2 | [0][1]+[1][0] | / | / | /| / | / | / | | 3 | / | [1][2]+[2][1] | / | /| / | / | [m-2][n-1] | | m-1 | / | / | / | /| / | [m-1][n-2] | end |

  4. 我们想要走到end,根据题意必须要走过n-1或者m-1。若因此end = (m-1)+(n-1)。如上表。

  5. 我们可以得出中间部分的解 sumgrid[i][j] = sumgrid[i-1][j]+sumgrid[i][j-1]。

  6. 题目中的解即为sumgrid[m-1][n-1]

var uniquePaths = function(m, n) {
    //创建一个二维数组,填充1
    let sumgrid = new Array(m).fill(1).map(item => new Array(n).fill(1))
    sumgrid[0][0] = 1
    for(i = 0; i < m; i++){
        if(i === 0){
            //只能往右走到达,第一行全部为1
            for(j = 1; j < n; j++){
                sumgrid[i][j] = 1
            }
        }else{
            //只能往下走到达,第一列全部为1
             for(j = 0; j < n; j++){
                if(j === 0){
                    sumgrid[i][j] =1
                }else{
                    sumgrid[i][j] = sumgrid[i-1][j]+sumgrid[i][j-1]
                }
            }
        }
    }
    return sumgrid[m-1][n-1]
};

结果

10cd5a7324e64044d96c03baedb0b6c.png

优化(减少无用数据)只保留上一行的数据(简化为一维数组问题)

1111111
1234567
1234567
13610152128
var uniquePaths = function(m, n) {
    let sumgrid = new Array(n).fill(1)
    for(i = 1; i < m; i++){
        sumgrid[0] = 1
        for(j = 1; j < n; j++){
              sumgrid[j] = sumgrid[j-1]+sumgrid[j]
        }
    }
    return sumgrid[n-1]
};

结果

image.png

最优解问题

最多,最少,最快...

image.png

我的解题思路

  1. 明确目标:走过的路,路径和最小,只能向下或者向右移动一步,输出的是路径和

  2. ❌一开始使用反推的方法,最后一个格子,只可能是来自上或者左,所以我两者中使用Math.min(a,b)来取较小值,例子中,我发现我有两个2了,那我要取哪个...我瞬间就不理解了。

  3. ✔本质上,题目是需要我们算出走到每个格子所需要的最少路径,因此我们可以新建一个二维数组,记录每一个格子的最少路径值,该数组的最后一项即为题目的答案

  4. 实现

    • 第一行, 最小的走法是: 一个挨着一个往右, 每个格子的最小值就是grid第一行的累加和。
    • 第一列, 最小的走法是: 一个挨着一个往下, 每个格子的最小值就是grid第一列的累加和。
    • 一般位置, 要么从左边来, 要么从上边来, 哪个小选择从哪边来。

代码

var minPathSum = function (grid) {
    //取行
    let m = grid.length
    //取列
    let n = grid[0].length
    //二位数组变为0
    let sumgrid = new Array(m).fill(0).map(item => new Array(n).fill(0))
    for (let i = 0; i < m; i++) {
        for (let j = 0; j < n; j++) {
            //对于第一排的,只能从左边加过来
            if (i == 0) {
                if (j == 0) {
                    sumgrid[0][0] =grid[0][0]
                    continue
                }else{
                    sumgrid[0][j]=sumgrid[0][j-1]+grid[0][j]
                    continue
                }
            }
            //对于第一列的,只能从上边加过来
            if(j==0){
                sumgrid[i][0]=sumgrid[i-1][0]+grid[i][0]
                continue
            }
            //对于其他的,找上面或者左边两者之间的更小的一个与自己相加
             sumgrid[i][j]=Math.min(sumgrid[i-1][j],sumgrid[i][j-1])+grid[i][j]
        }
    }
    // 最小值就是sum二维度数组中sum[m-1][n-1]的值
    return sumgrid[m-1][n-1]
};

参考文章地址

juejin.cn/post/684490…

题目地址

背包背包:

leetcode-cn.com/problems/ta…

路径:

leetcode-cn.com/problems/un…
leetcode-cn.com/problems/mi…