代码随想录-动态规划专题

文章介绍了动态规划在几个经典问题中的应用,如斐波那契数列、爬楼梯的不同路径、最小花费爬楼梯、整数拆分以及二叉搜索树的计数,通过递推公式和dp数组来求解最优解。
摘要由CSDN通过智能技术生成

        有N件物品和一个最多能背重量为W 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。

 步骤分为以下五步:

  1. 确定dp数组(dp table)以及下标的含义
  2. 确定递推公式
  3. dp数组如何初始化
  4. 确定遍历顺序
  5. 举例推导dp数组

509. 斐波那契数 

斐波那契数,通常用 F(n) 表示,形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是: F(0) = 0,F(1) = 1 F(n) = F(n - 1) + F(n - 2),其中 n > 1 给你n ,请计算 F(n) 。

示例 1:

  • 输入:2
  • 输出:1
  • 解释:F(2) = F(1) + F(0) = 1 + 0 = 1
int fib(int n){
    if(n<=1){
        return n;
    }
    int dp[n+1];
    dp[0]=0;
    dp[1]=1;
    for(int i=2;i<=n;++i){
        dp[i]=dp[i-1]+dp[i-2];
    }
    return dp[n];
}

70. 爬楼梯 

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

注意:给定 n 是一个正整数。

示例 1:

  • 输入: 2
  • 输出: 2

        dp[i]: 爬到第i层楼梯,有dp[i]种方法 

        首先是dp[i - 1],上i-1层楼梯,有dp[i - 1]种方法,那么再一步跳一个台阶不就是dp[i]。

        然后是dp[i - 2],上i-2层楼梯,有dp[i - 2]种方法,那么再一步跳两个台阶不就是dp[i]。

        那么dp[i]就是 dp[i - 1]与dp[i - 2]之和。

        递推公式:dp[i] = dp[i - 1] + dp[i - 2]

int climbStairs(int n) {
    if(n<=1){
        return n;
    }
    int dp[n+1];
    dp[1]=1;
    dp[2]=2;
    for(int i=3;i<=n;++i){
        dp[i]=dp[i-1]+dp[i-2];
    }
    return dp[n];
}

        这道题目还可以继续深化,就是一步一个台阶,两个台阶,三个台阶,直到 m个台阶,有多少种方法爬到n阶楼顶。  

        只需要将第二个循环中的2,换成m,就能用于其他题。

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

746. 使用最小花费爬楼梯

数组的每个下标作为一个阶梯,第 i 个阶梯对应着一个非负数的体力花费值 cost[i](下标从 0 开始)。每当你爬上一个阶梯你都要花费对应的体力值,一旦支付了相应的体力值,你就可以选择向上爬一个阶梯或者爬两个阶梯。请你找出达到楼层顶部的最低花费。在开始时,你可以选择从下标为 0 或 1 的元素作为初始阶梯。

示例 1:

  • 输入:cost = [10, 15, 20]
  • 输出:15
  • 解释:最低花费是从 cost[1] 开始,然后走两步即可到阶梯顶,一共花费 15 。
int minCostClimbingStairs(int* cost, int costSize) {
    int dp[costSize+1];
    dp[0]=0;
    dp[1]=0;
    for(int i=2;i<=costSize;++i){
        dp[i]=fmin(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2]);
    }
    return dp[costSize];
}


62. 不同路径

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。

问总共有多少条不同的路径?

输入:m = 3, n = 7
输出:28

1、dp[i][j] :表示从(0 ,0)出发,到(i, j) 有dp[i][j]条不同的路径。 

2、dp[i][j] = dp[i - 1][j] + dp[i][j - 1],因为dp[i][j]只有这两个方向过来。

3、首先dp[i][0]一定都是1,因为从(0, 0)的位置到(i, 0)的路径只有一条,那么dp[0][j]也同理。

4、递推公式dp[i][j] = dp[i - 1][j] + dp[i][j - 1],dp[i][j]都是从其上方和左方推导而来,那么从左到右一层一层遍历就可以了。

int uniquePaths(int m, int n) {
    int dp[m][n];
    dp[0][0]=1;
    //初始化
    for(int i=0;i<n;++i){
        dp[0][i]=1;
    }
    for(int j=0;j<m;++j){
        dp[j][0]=1;
    }
    //递推
    for(int i=1;i<m;++i){
        for(int j=1;j<n;++j){
            dp[i][j]=dp[i-1][j]+dp[i][j-1];
        }
    }
    return dp[m-1][n-1];
}


63. 不同路径 II

 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish”)。现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?

网格中的障碍物和空位置分别用 1 和 0 来表示。

输入:obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]]
输出:2
解释:3x3 网格的正中间有一个障碍物。
从左上角到右下角一共有 2 条不同的路径:
1. 向右 -> 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右 -> 向右
int uniquePathsWithObstacles(int** obstacleGrid, int obstacleGridSize, int* obstacleGridColSize) {
    int n=obstacleGridSize,m=*obstacleGridColSize;
    int dp[n][m];
    memset(dp, 0, sizeof(dp));
    //初始化行和列
    for (int i=0; i<n; i++){      
        if(obstacleGrid[i][0]==1) break;//出现障碍物,停止赋值
        dp[i][0]=1;     
    }
    for (int j=0; j<m; j++){
        if(obstacleGrid[0][j]==1)  break;
        dp[0][j]=1;   
    }
    for(int i=1;i<n;++i){
        for(int j=1;j<m;++j){
            if(obstacleGrid[i][j]==0) 
                dp[i][j]=dp[i-1][j]+dp[i][j-1];
        }
    }
    return dp[n-1][m-1];
}


343. 整数拆分 

给定一个正整数 n ,将其拆分为 k 个 正整数 的和( k >= 2 ),并使这些整数的乘积最大化。

返回 你可以获得的最大乘积 。

输入: n = 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1。

        1、确定dp数组(dp table)以及下标的含义

dp[i]:分拆数字i,可以得到的最大乘积为dp[i]。

dp[i]的定义将贯彻整个解题过程,下面哪一步想不懂了,就想想dp[i]究竟表示的是啥!

        2、确定递推公式

可以想 dp[i]最大乘积是怎么得到的呢?其实可以从1遍历j,然后有两种渠道得到dp[i].

一个是j * (i - j) 直接相乘。

一个是j * dp[i - j],相当于是拆分(i - j),对这个拆分不理解的话,可以回想dp数组的定义。

        3.初始化数组

初始化dp[2] = 1,从dp[i]的定义来说,拆分数字2,得到的最大乘积是1,这个没有任何异议!

        4、确定遍历顺序

确定遍历顺序,先来看看递归公式:dp[i] = max(dp[i], max((i - j) * j, dp[i - j] * j));

dp[i] 是依靠 dp[i - j]的状态,所以遍历i一定是从前向后遍历,先有dp[i - j]再有dp[i]。

int integerBreak(int n) {
    int dp[n+1];
    memset(dp,0,sizeof(dp));
    for(int i=2;i<=n;++i){
        for(int j=1;j<i;++j){
            dp[i]=fmax(dp[i],fmax((i-j)*j,dp[i-j]*j));
        }
    }
    return dp[n];
}


96. 不同的二叉搜索树 

给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。

输入:n = 3
输出:5

        1、dp[i] : 1到i为节点组成的二叉搜索树的个数为dp[i]

        2、递推公式:dp[i] += dp[j - 1] * dp[i - j]; ,j-1 为j为头结点左子树节点数量,i-j 为以j为头结点右子树节点数量

       3、从递归公式上来讲,dp[以j为头结点左子树节点数量] * dp[以j为头结点右子树节点数量] 中以j为头结点左子树节点数量为0,也需要dp[以j为头结点左子树节点数量] = 1, 否则乘法的结果就都变成0了。所以初始化dp[0] = 1

       4、递归公式:dp[i] += dp[j - 1] * dp[i - j]可以看出,节点数为i的状态是依靠 i之前节点数的状态。

       5、首先一定是遍历节点数,从递归公式:dp[i] += dp[j - 1] * dp[i - j]可以看出,节点数为i的状态是依靠 i之前节点数的状态。那么遍历i里面每一个数作为头结点的状态,用j来遍历。

int numTrees(int n) {
    int dp[n+1];
    memset(dp,0,sizeof(dp));
    dp[0]=1;
    dp[1]=1;
    for(int i=2;i<=n;++i){
        for(int j=1;j<=i;++j){
            //对于第i个节点,需要考虑1作为根节点直到i作为根节点的情况,所以需要累加
            //一共i个节点,对于根节点j时,左子树的节点个数为j-1,右子树的节点个数为i-j
            dp[i]+=dp[j-1]*dp[i-j];
        }
    }
    return dp[n];
}

 

  • 10
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值