彻底告别DP黑盒之基础篇

DP五部曲(刚学)拿来用用、验其真假,毕竟DP这个小妖精,曾经带给我们痛苦的回忆,为了递推表达式苦苦挣扎,稍微改变题目就被按在地上狠狠摩擦,误以为自己一点灵活机变的能力都没有,对其闻风丧胆、望风而逃。如今,有了些胆识(实则是被迫营业)来直面风暴吧!少年!

本篇是基础篇,省略了斐波那契和爬楼梯这种小白题目,直接从最小费用爬楼梯下刀!

后面还会出各个问题的相应专题,今天先练个手。
在这里插入图片描述
注:前面标号为LC(力扣)标号,读者可对应刷题。

DP五步曲

1.明白DP数组下标含义(雨巨强调的重点)
2.推出递推表达式(自不必说)
3.确定初始值(正确的起步很重要)
4.明了遍历顺序(这个不明白,背包问题会吃大亏)
5.懂得打印DP数组验证想法(懂得自我检验而不是盲目狂抄代码)

最小费用爬楼梯

LC 第746题

我是觉得这个题目描述很迷,不知你们有没有这种感觉哈
它那个下标有些问题,没办法用实际的例子理解透。
唯一能做的是顺应五步曲,并且充分信任你之前计算的DP数组的结果!
在这里插入图片描述

我这样考虑是从:你踏上第 i 级台阶时,价钱已经在之前的第(i - 1)或者第(i - 2)级台阶上付过了。
所以你在第0或者第1级上没有价格,因为根本不存在之前付钱这一说。
因为DP数组的含义是踏上第 i 级台阶支付的最小费用,因此必须从 1 ~ i 都要有数值。
最后的结果是第 s 级的最小费用,也就是最后一级台阶(那个并不存在的终点——就是要抵达所有台阶之上)。

class Solution {
public:
    int minCostClimbingStairs(vector<int>& cost) {
        int s = cost.size();
        int dp[s+1];
        dp[0] = 0, dp[1] = 0;
        for(int i = 2; i <= s; i++){
            dp[i] = min(dp[i-1] + cost[i-1], dp[i-2] + cost[i-2]);
        }
        return dp[s];
    }
};

如果仍对cost的下标有问题,请参见题目原话

第 i 个阶梯对应着一个非负数的体力花费值 cost[i](下标从 0 开始)。

不同路径

LC 第62题

这题似曾相识,快速AC,老规矩,先上五步曲
在这里插入图片描述

class Solution {
public:
    int uniquePaths(int m, int n) {
        if(m == 1 & n == 1) return 1;
        int dp[m+1][n+1];
        dp[1][1] = 0;
        for(int i = 2; i <= m; i++) dp[i][1] = 1;
        for(int i = 2; i <= n; i++) dp[1][i] = 1;
        for(int i = 2; i <= m; i++){
            for(int j = 2; j <= n; j++){
                dp[i][j] = dp[i-1][j] + dp[i][j-1];
            }
        }
        return dp[m][n];
    }
};

不同路径Ⅱ

LC 第63题

这题思路很简单,但是一个叫做容器的东西却磨了我很久的时间。因为二维数组初始化时,列必须确定,所以这里不能用二维数组。得用容器。而且容器下标得从0开始(因为给的obstacleGrid的是从0开始的哦)

在这里插入图片描述

class Solution {
public:
    int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
        int m = obstacleGrid.size();
        int n = obstacleGrid[0].size();
        vector<vector<int>> dp(m, vector<int>(n, 0));
        for(int i = 0; i < m && obstacleGrid[i][0] == 0; i++) dp[i][0] = 1;
        for(int i = 0; i < n && obstacleGrid[0][i] == 0; i++) dp[0][i] = 1;
        for(int i = 1; i < m; i++){
            for(int j = 1; j < n; j++){
                if(obstacleGrid[i][j]) dp[i][j] = 0;
                else dp[i][j] = dp[i-1][j] + dp[i][j-1];
            }
        }
        return dp[m-1][n-1];
    }
};

整数拆分

LC 第343题

这一题的第一步很重要——就是如何定义DP数组?

我一开始的想法是dp[ i ]表示将 n 拆分为 i 个数字相加的最大积,但想了半个小时,发现根本没办法进行递推。

此路不通就换一条啊!傻!

在这里插入图片描述

在这里插入图片描述
对小一点的数而言,最大乘积都是拆分为两个数而来的,所以如果不加 j * (i - j)可能连前面的基础数都算不对。

因为对于 dp[i] 而言,只有两种来历,一种是 j * (i - j),一种就是 j * dp[i - j]

class Solution {
public:
    int integerBreak(int n) {
        int dp[n+1];
        memset(dp, 0, sizeof dp);
        dp[2] = 1;
        for(int i = 3; i <= n; i++){
            for(int j = 1; j <= i - 2; j++){
                dp[i] = max(dp[i], max(j * (i - j), j * dp[i - j]));
            }
        }
        return dp[n];
    }
};

不同的二叉搜索树

LC 第96题

没有多余的想法,就是老老实实地找规律,另外还要明白二叉搜索树的定义

二叉查找树(Binary Search Tree),(又:二叉搜索树,二叉排序树)它或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树。

在这里插入图片描述

class Solution {
public:
    int numTrees(int n) {
        if(n == 1) return 1;
        int dp[n+1];
        memset(dp, 0, sizeof dp);
        dp[0] = 1, dp[1] = 1, dp[2] = 2;
        for(int i = 3; i <= n; i++){
            for(int j = 1; j <= i; j++){
                dp[i] += (dp[j-1] * dp[i-j]);
            }
        }
        return dp[n];
    }
}; 

总结

基础篇还算比较简单的,我们DP数组的定义都是直接根据题目的问题来,它要我们求什么,我们就怎么定义,过了第一关后面的步骤就都好说了。

让我们一起期待提高篇吧!磨刀霍霍ing!

在这里插入图片描述
从废墟上站立起来!I totally believe in us!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值