动态规划的简单套路(C++描述)

动态规划

作者:Cukor丘克

文章目录

  • 动态规划
    • 引导
    • 计数
    • 最值
    • 存在性

通过例子学习动态规划。动态规划是解决递归重复计算的方法。

能解决的问题:

  1. 计数
  2. 最值
  3. 存在性

解题步骤:

  1. 最后一步
  2. dp数组的含义
  3. 状态转移方程
  4. dp数组的初始化
  5. 实例代入

使用的编程语言的是C++.

引导

斐波那契数列

什么是斐波那契数列?

前两个数字是1,从第3个数开始,是它的前两个数之和。

代码实现:

递归方式:

int fib(int n){
    if(n == 0) return 0;
    if(n == 1) return 1;
    return fib(n-1) + fib(n-2);
}

动态规划:

// 最后一步:前面的数据已经算好,最后一个数据只需要拿到它的前两个数据即可。
// dp数组的含义:第i个数的斐波那契数是dp[i]
// 状态转移方程:dp[i] = dp[i-1] + dp[i-2]
// dp数组的初始化:dp[0] = 0; dp[1] = 1;
// 实例代入:dp[5] = dp[4] + dp[3];
//            dp[4] = dp[3] + dp[2];    dp[3] = dp[2] + dp[1];        
//            dp[2] = dp[1] + dp[0];    
// 过程:dp[5] = 5;
//            dp[4] = 3    dp[3] = 2;        
//            dp[2] = 1;
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];
}

计数

  • 爬楼梯
  • 机器人走路

爬楼梯:你爬楼梯,你可以一次走一步,也一次可以走两个。现有n阶楼梯,你有多少种方法爬到顶部。

/*
最后一步:要么第n-1阶,要么n-2阶,只需要知道走到n-1阶有多少种方法,和走到n-2阶有多少种方法。

dp数组的含义:走到n阶楼梯的方法数是dp[n]

状态转移方程:dp[n] = dp[n-1] + dp[n-2]

初始化:dp[1] = 1; dp[2] = 2; 
*/
int climb_stairs(int n) {
    if (n <= 1)
        return n;
    vector<int> dp(n + 1);
    dp[1] = 1;
    dp[2] = dp[1] + 1;
    for (int i = 3; i <= n; i++) {
        dp[i] = dp[i - 1] + dp[i - 2];
    }
    return dp[n];
}

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

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

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

/*
最后一步:最后一步的前一个状态,要么在(m-1,n)的位置,要么在(m,n-1)的位置,只需要再向下或向右走一步即可到达(m,n)
        所以走到(m,n)位置的方法数位走到(m-1,n)的方法数和走到(m,n-1)的方法数之和。
dp数组的含义:(0,0)位置走到(i,j)位置的方法数是dp[i][j]
状态转移方程:dp[i][j] = dp[i-1][j] + dp[i][j-1];
初始化:第一行和第一列都是1 dp[i][0] = 1; dp[0][j] = 1;
*/
int uniquePaths(int m, int n) {
    vector<vector<int>> dp(m,vector<int>(n));
    for(int i = 0; i < m; i++) {
        dp[i][0] = 1;
    }
    for(int j = 0; j < n; j++) {
        dp[0][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];
}
//-----------------------------------------------

int Solution::uniquePaths(int m, int n) {
    std::vector<std::vector<int>> dp(m, std::vector<int>(n));
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            if (i == 0 || j == 0) {
                dp[i][j] = 1;
            } else {
                dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
            }
        }
    }
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            std::cout << dp[i][j] << " ";
        }
        std::cout << "\n";
    }
    std::cout << std::endl;
    return dp[m - 1][n - 1];
}

最值

  • 打家劫舍
  • 买东西(硬币支付问题)

打家劫舍:你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警

给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

题目的别名:求不相邻元素之和最大。

/*
最后一步:前i-1个数已经确定最大和,只需要判断第i个数是否纳入最大和中。
        纳入:最大和则更新为前i-2时的状态加当前数组中的值。
        不纳入:第i-1个状态
dp数组的含义:前i个数确定不相邻元素之和最大和为dp[i]
状态转移方程:dp[i] = max(dp[i-2] + nums[i],dp[i-1]);
初始化:dp[0] = nums[0]; dp[1] = max(dp[0], nums[1]);
*/
int rob(vector<int> &nums){
    int n = nums.size();
    vector<int> dp(n);
    dp[0] = nums[0]; 
    dp[1] = max(dp[0], nums[1]);
    for (int i = 2; i < n; i++) {
        dp[i] = max(dp[i-2] + nums[i],dp[i-1]);
    }
    return dp[n-1];
}

买东西(硬币支付问题):给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1

你可以认为每种硬币的数量是无限的。

/*
最后一步:前面已经确定使用硬币数最少的方案。只需要确定最后一枚硬币到底是哪种面额的。如果剩余的钱不能用已有的面额硬币支付,则无法确            定最少硬币数。
dp数组的含义:i元的商品使用的硬币数最少是dp[i]
状态转移方程:dp[i] = min(dp[i - coins[j]]+1, dp[i])
初始化:dp[0] = 0
        dp[i] = INT_MAX,   i = 1,2,...,n
*/
int coinChange(vector<int>& coins, int n) {
    vector<int> dp(n+1, INT_MAX);
    dp[0] = 0;
    int m = coins.size();
    for (int i = 1; i <= n; i++) {
        for (int j = 0; j < m; j++){
            if (i >= coins[j] && dp[i - coins[j]] != INT_MAX){
                dp[i] = min(dp[i - coins[j]] + 1, dp[i]);
            } 
        }
    }
    return dp[n] == INT_MAX ? -1 : dp[n];
}

存在性

  • 拼数字

拼数字:在一个数组里面找到一些元素相加起来是否和num的值相等,如果能拼成num则返回true,否则返回false.

例子:

num=11

arr ={4,5,2,7}

返回true

num=3

arr ={4,5,2,7}

返回false

/*
最后一步:如果最后一步的前一个状态能拼成num,则最后一个状态也能拼成num,
          如果最后一步的前一个状态不能拼成num,就看加上数组的最后一个数能否拼成num,
          如果能就返回true,否则返回false.
dp数组的含义:i+1个数的数组能否拼成j,能则dp[i][j]=true,不能则dp[i][j]=false
状态转移方程:if(dp[i-1][j] == true) dp[i][j] = true
              else dp[i][j] = dp[i-1][j-arr[i]];
初始化:dp[0][j] = false; j = 0,1,...,num
        dp[0][arr[0]] = true;
*/


bool spell_numbers(vector<int>& arr, int num){
  int n = arr.size();
  vector<vector<bool>> dp(n,vector<bool>(num+1));
  for(int j = 0; j <= num; j++) {
    dp[0][j] = false;
  }
  if (num >= arr[0])
    dp[0][arr[0]] = true;
  for (int i = 1; i < n; i++) {
    for (int j = 0; j <= num; j++) {
      dp[i][j] = dp[i-1][j];
      if (j >= arr[i])
        dp[i][j] = j == arr[i] || dp[i-1][j] || dp[i-1][j-arr[i]];
    }
  }
  return dp[n-1][num];
}

动态规划的简单入门套路就先到这了,以下是我在b站的视频教程。

动态规划的简单套路(C++描述)


谢谢大家的支持啦!!!

你可能感兴趣的:(算法,c++,动态规划,算法)