第零章:动态规划解题套路框架

labuladong动态规划解题套路框架


动态规划之背包汇总

第零章:动态规划解题套路框架
第一章:经典动态规划:0-1 背包问题
第零章:经典动态规划:子集背包问题
第零章:经典动态规划:完全背包问题


动态规划一般采用自底向上的方式求最值
求解动态规划的核心问题是穷举,但是穷举过程中会存在重叠子问题,所以加上备忘录来优化过程
dp 三要素:

  1. 明确 base case
  2. 明确「状态」-> 明确「选择」, 即状态转移方程
    • 确定「状态」,也就是原问题和子问题中会变化的变量
    • 确定「选择」,也就是导致「状态」产生变化的行为
  3. 明确 dp数组/函数的含义

因此,dp 框架可写成:

# 初始化 base case
dp[0][0][...] = base
# 进行状态转移
for 状态1 in 状态1的所有取值:
    for 状态2 in 状态2的所有取值:
        for ...
            dp[状态1][状态2][...] = 求最值(选择1,选择2...)

509. 斐波那契数

class Solution(object):
    def fib(self, N):
        """
        :type N: int
        :rtype: int
        """
        if N==0: return 0
        dp=[0 for _ in range(N+1)]
        dp[1]=1
        for i in range(2,N+1):
            dp[i]=dp[i-1]+dp[i-2]
        return dp[-1]
//#include <iostream>
//#include <vector>
//using namespace std;
class Solution{
public:
    int fib(int N){
        if (N==0)
            return 0;
        vector<int> dp(N+1);
        dp[1]=1;
        for (int i = 2; i <N+1; i++) {
            dp[i]=dp[i-1]+dp[i-2];
        }
        return dp[N];
    }
};
//int main(){
//    int s=Solution().fib(5);
//    cout<<s<<endl;
//}

322. 零钱兑换

方法1:二维dp数组

dp[i][j]的含义:前i枚硬币凑成j块钱所需要的最小硬币数

  • 因为求最小的硬币数,所以数组先初始化为较大的amount+1(最多需要amount+1枚硬币)
  • 加一个虚拟的0行0列,第0行表示当前硬币为0,第0列表示当前金额数为0。第i行对应的硬币为第coins[i-1]个硬币
  • 若金额大于当前硬币,则取装和不装的最小值
    • 不装当前硬币:dp[i][j]=dp[i-1][j]
    • 装当前硬币:dp[i][j]=dp[i][j-coins[i-1]]+1
      • 注意dp[i][j]表示的是前i枚硬币凑成j块钱所需要的最小硬币数,这里的i已经包括了用和不用当前硬币, 所以不管是不是第一次用当前硬币,当前状态dp[i][j]都从dp[i][j-coins[i-1]]+1转移而来
  • 若金额小于当前硬币,则不能装当前硬币:dp[i][j]=dp[i-1][j]
class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        if (amount<0) return -1;
        vector<vector<int>>dp(coins.size()+1,vector<int>(amount+1,amount+1));
        for (int i = 0; i < coins.size()+1; ++i) {
            dp[i][0]=0;
        }
        for (int i = 1; i < coins.size()+1; ++i) {
            for (int j = 0; j < amount+1; ++j) {
                if (j>=coins[i-1]){
                    dp[i][j]=min(dp[i-1][j],dp[i][j-coins[i-1]]+1);
                }
                else{
                    dp[i][j]=dp[i-1][j];
                }
            }
        }
        return (dp[coins.size()][amount]==amount+1)?-1:dp[coins.size()][amount];
    }
};

方法2:一维dp数组

  • 明确dp数组含义:凑好当前零钱数的最小硬币数量
  • 明确 base case:dp[0]=0。 初始化时dp[i] 设为float("inf") 最大值
  • 明确状态:零钱大小
  • 明确选择:硬币。 if i>=coin 时才做选择(零钱大小至少要比硬币值大)
class Solution(object):
    def coinChange(self, coins, amount):
        dp=[float("inf") for _ in range(amount+1)]
        dp[0]=0
        for i in range(1,amount+1):
            for coin in coins: # 相当于在确定第i个值时,取所有(可行)硬币的状态转移的最小值+1
                if i>=coin:
                    dp[i]=min(dp[i],dp[i-coin]+1)
        return dp[-1] if dp[-1]!=float("inf") else -1 
//#include <vector>
//#include <iostream>
//using namespace std;
class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        vector<int> dp(amount+1,amount+1);
        dp[0]=0;
        for (int i = 1; i < amount+1; ++i) {
            for (int coin:coins) {
                if (coin<=i)
                    dp[i]=min(dp[i],dp[i-coin]+1);
            }
        }
        return (dp.back()==amount+1)?-1:dp.back();

    }
};

//int main(){
//    vector<int> coins={1, 2, 5};
//    int s=Solution().coinChange(coins,11);
//    cout<<s<<endl;
//}

   转载规则


《第零章:动态规划解题套路框架》 M 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
第零章:回溯算法解题套路框架 第零章:回溯算法解题套路框架
labuladong回溯算法解题套路框架 解决一个回溯问题,实际上就是一个决策树的遍历过程。主要考虑的问题有: 路径:也就是已经做出的选择。 选择列表:也就是你当前可以做的选择。 结束条件:也就是到达决策树底层,无法再做选择的条件。
2020-07-24
下一篇 
第零章:学习算法和刷题的框架思维 第零章:学习算法和刷题的框架思维
labuladong学习算法和刷题的框架思维 124. 二叉树中的最大路径和_后序遍历 采用后序遍历:先访问左子树,再访问右子树,最后根据左右子树的结果和当前节点更新当前节点对应的最大值 maxVal 记录的是全局最大路径和(可以不返回,因
2020-07-23
  目录