Leetcode—动态规划(基础问题)

动规五部曲:

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

基础题目

1、斐波那契数

五部曲:

确定dp数组以及下标的含义:第i个斐波那契数值

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

dp数组如何初始化:dp[0]=0,dp[1]=1,其余未知先置为0

确定遍历顺序:从前往后

举例推导dp数组

class Solution {
public:
    int fib(int n) {
        if(n<=1) return n;
        vector<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];
    }
};

2、 爬楼梯

五部曲:

确定dp数组以及下标的含义:爬到第i层楼梯,有dp[i]种方法

确定递推公式:爬到第一层楼梯有一种方法,爬到二层楼梯有两种方法。那么第一层楼梯再跨两步就到第三层 ,第二层楼梯再跨一步就到第三层。所以到第三层楼梯的状态可以由第二层楼梯 和 到第一层楼梯状态推导出来。从dp[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]

dp数组如何初始化:dp[1]=1,dp[2]=2,没有第0阶,所以不用考虑dp[0]

确定遍历顺序:从前往后

举例推导dp数组

class Solution {
public:
    int climbStairs(int n) {
        //斐波那契数变体
        if(n<=1) return n;
        vector<int> dp(n+1);
        dp[1] = 1;//爬一层只有一种方法
        dp[2] = 2;//爬两层有两种方法

        //爬到第n层的方式可以是在第n-1层的基础上加一
        //还有在n-2层的基础上加二

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

        return dp[n];
    }
};

3、使用最小花费爬楼梯

五部曲:

确定dp数组以及下标的含义:到达第i阶台阶所花费的最少体力为dp[i];

确定递推公式:dp[i] = min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2]);由题目可知,每次可以爬1~2个台阶,dp[i - 1] 跳到 dp[i] 需要花费 dp[i - 1] + cost[i - 1];dp[i - 2] 跳到 dp[i] 需要花费 dp[i - 2] + cost[i - 2]。因为要求最小花费,所以要使用min函数;

dp数组如何初始化:dp[0]=0,dp[1]=0,题目中说可以从0阶或者1阶开始;

确定遍历顺序:从前往后

举例推导dp数组

class Solution {
public:
    int minCostClimbingStairs(vector<int>& cost) {
        int n = cost.size();

        //定义dp数组
        vector<int> dp(n+1);//到达第i阶台阶所花费的最少体力为dp[i]

        //初始化dp数组,可以从从下标为 0 或下标为 1 的台阶开始爬楼梯
        dp[0] = 0;
        dp[1] = 0;

        for(int i = 2;i<=n;i++)
        {
            dp[i] = min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2]);
        }

        return dp[n];
    }
};

4、不同路径

五部曲:

确定dp数组以及下标的含义:dp[i][j] = k 表示走到(i,j)位置有k种方法;

确定递推公式:dp[i][j] = dp[i-1][j] + dp[i][j-1];走到第(i,j)位置的话只有两种方法,从(i-1,j)过去,或者(i,j-1)过去;

dp数组如何初始化:dp[i][0]=1,dp[0][j]=1,因为机器人只能向下或者下右移动;

确定遍历顺序:从左向右,从上向下

举例推导dp数组

class Solution {
public:
    int uniquePaths(int m, int n) {
        //定义dp数组,dp[i][j] = k 表示走到(i,j)位置有k种方法
        vector<vector<int>> dp(m,vector<int>(n,0));
        //因为机器人只能向下或者下右移动,所以
        for(int i=0;i<m;i++) dp[i][0] = 1;
        for(int j=0;j<n;j++) dp[0][j] = 1;

        //走到第(i,j)位置的话只有两种方法,从(i-1,j)过去,或者(i,j-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];
    }
};

5、不同路径Ⅱ

五部曲:

确定dp数组以及下标的含义:dp[i][j] = k 表示走到(i,j)位置有k种方法;

确定递推公式:dp[i][j] = dp[i-1][j] + dp[i][j-1];走到第(i,j)位置的话只有两种方法,从(i-1,j)过去,或者(i,j-1)过去;

dp数组如何初始化:

        for(int i=0;i<m;i++) 
        {
            if(obstacleGrid[i][0]==0) dp[i][0] = 1;
            else break;
        }
        for(int j=0;j<n;j++) 
        {
            if(obstacleGrid[0][j]==0) dp[0][j] = 1;
            else break;
        }

确定遍历顺序:从左向右,从上向下

举例推导dp数组:

       for(int i = 0;i<m;i++)

        {

            for(int j = 0;j<n;j++)

                cout<<dp[i][j];

            cout<<endl;

        }

输出:

class Solution {
public:
    int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
        //定义dp数组,dp[i][j] = k 表示走到(i,j)位置有k种方法
        int m = obstacleGrid.size();
        int n = obstacleGrid[0].size();
        vector<vector<int>> dp(m,vector<int>(n,0));
        //因为机器人只能向下或者下右移动,遇见障碍物的话当前位置以及之后位置可达方式均为0
        for(int i=0;i<m;i++) 
        {
            if(obstacleGrid[i][0]==0) dp[i][0] = 1;
            else break;
        }
        for(int j=0;j<n;j++) 
        {
            if(obstacleGrid[0][j]==0) dp[0][j] = 1;
            else break;
        }
        //走到第(i,j)位置的话只有两种方法,从(i-1,j)过去,或者(i,j-1)过去
        for(int i = 1;i<m;i++)
        {
            for(int j = 1;j<n;j++)
            {
                //判断一下当前位置是否有障碍物,有的话,当前位置的dp数组值为0
                if(obstacleGrid[i][j] == 1)
                    dp[i][j] = 0;
                else
                    dp[i][j] = dp[i-1][j] + dp[i][j-1]; 
            }
        }

        return dp[m-1][n-1];
    }
};

6、整数拆分

五部曲:

确定dp数组以及下标的含义:dp[i]表示拆解数字i( 1 <= i <= n)得到的最大乘积为dp[i];

确定递推公式:dp[i] = max(dp[i],max(j*(i-j),j*dp[i-j]));当i被拆分成两个数时,dp[i] = j*(i-j);当i被拆分成两个以上时,dp[i] = j*dp[i-j](例如10拆分成4+3+3,最大乘积既可以由4*dp[6]得到);

dp数组如何初始化:0和1的拆分没有意义,从2开始拆分,2只能拆分成1+1,所以dp[2]=1;

确定遍历顺序:从左向右

举例推导dp数组

for(int i=2;i<=n;i++)
   cout<<dp[i]<<' ';

 

class Solution {
public:
    int integerBreak(int n) {
        //定义dp数组:dp[i]表示拆解数字i( 1 <= i <= n)得到的最大乘积为dp[i]
        vector<int> dp(n+1,0);
        //0和1的拆分没有意义,从2开始拆分,2只能拆分成1+1,所以dp[2]=1
        dp[2] = 1;
        //i从3开始一直到n
        for(int i = 3;i <= n;i++)
        {
            for(int j = 1;j < i;j++)//j代表从i中拆分出来的数,所以小于i
            {
                //当i被拆分成两个数时,dp[i] = j*(i-j)
                //当i被拆分成两个以上时,dp[i] = j*dp[i-j](例如10拆分成4+3+3,最大乘积既可以由4*dp[6]得到)
                dp[i] = max(dp[i],max(j*(i-j),j*dp[i-j]));
            }
        }

        return dp[n];
    }
};

7、 不同的二叉搜索树

五部曲:

直接看代码随想录解释:代码随想录 (programmercarl.com)icon-default.png?t=N7T8https://www.programmercarl.com/0096.%E4%B8%8D%E5%90%8C%E7%9A%84%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91.html#%E6%80%9D%E8%B7%AF

class Solution {
public:
    int numTrees(int n) {
        //定义dp数组:dp[i]表示i个节点时有dp[i]种不同的二叉搜索树
        vector<int> dp(n+1,0);
        //初始化
        dp[0] = 1;

        //递推开始
        for(int i = 1;i<=n;i++)
        {
            for(int j = 1;j<=i;j++)//j是根节点元素
            {
                dp[i] += dp[j-1]*dp[i-j];
            }
        }

        return dp[n];
    }
};

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值