动态规划题型总结

DP做题规律

问题分解的过程中,如果此时的状态与之前的状态相关,那么DP可以试一试。

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

简单的DP

  • 一维
    斐波那契数
    爬楼梯
    dp[i]=dp[i-1]+dp[i-2]

  • 二维
    (1) 不同路径
    (2) 整数拆分详解 递推公式难
    dp[i] 表示将i拆分所得最大的乘积,那么dp[i]可以从dp[i-j]*j 与 j*(i-j)得到;

  • dp[i-j]一定是将i-j拆分为两个或两个以上的数字和,因此这一项考虑的是将i拆分大于两个数字和的情况

  • j*(i-j)考虑的是将i拆分为两个数字和的情况

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

背包

在这里插入图片描述

01背包

  • 数组含义
  • dp[i][j] 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,最大价值总和
  • 遍历方法
  • 不放物品i:dp[i - 1][j]
  • 放物品i:dp[i - 1][j - weight[i]] + value[i]
 //从后往前
for(int i = 0; i < weight.size(); i++) { // 遍历物品
    for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量
        dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
    }
}

完全背包

物品无限

// 从前往后
for(int i = 0; i < weight.size(); i++) { // 遍历物品
    for(int j = weight[i]; j <= bagWeight ; j++) { // 遍历背包容量
        dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
    }
}

多重背包

每件物品最多有Mi件可用,把Mi件摊开,其实就是一个01背包问题了。

题型:

找子集,得到目标和 问题转化 数组含义

  • 分割等和子集 W V相同
    给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等
  • 最后一块石头的重量 II W V相同 问题转化 数组含义
    有一堆石头,每块石头的重量都是正整数。
    每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:
    如果 x == y,那么两块石头都会被完全粉碎; 如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。 最后,最多只会剩下一块石头。返回此石头最小的可能重量。如果没有石头剩下,就返回 0。
    本质上寻找将集合分为两个子集,使值尽可能接近

找组合,得到目标和

  • 目标和 问题转换 递推公式 初始化
    left - right = target。
    left + right=sum
    left - (sum - left) = target ->left = (target + sum)/2
    问题转化为装满left有多少方案
    注:转化为这样后不需要在考虑-号的事情了!!!简化了问题

多维度背包

  • 一和零
    给你一个二进制字符串数组 strs 和两个整数 m 和 n 。
    请你找出并返回 strs 的最大子集的大小,该子集中 最多 有 m 个 0 和 n 个 1 。

完全背包 dp求组合、排列个数

  • 零钱兑换 II
外层for循环遍历物品(钱币),内层for遍历背包(金钱总额)的情况。
for (int i = 0; i < coins.size(); i++) { // 遍历物品
    for (int j = coins[i]; j <= amount; j++) { // 遍历背包容量
        dp[j] += dp[j - coins[i]];
    }
}
这种遍历顺序中dp[j]里计算的是组合数

如果把两个for交换顺序,代码如下:

for (int j = 0; j <= amount; j++) { // 遍历背包容量
    for (int i = 0; i < coins.size(); i++) { // 遍历物品
        if (j - coins[i] >= 0) dp[j] += dp[j - coins[i]];
    }
}
此时dp[j]里算出来的就是排列数!

打家劫舍 递推公式加限制

给递推公式加一些条件

dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);

买卖股票 多状态dp 状态转移方法

  • 买卖股票的最佳时机 只能买卖一次
    给dp数组加入一些状态
    dp[i][0] 第i天持有股票
    dp[i][1] 第i天未持有股票
    dp[i][0] 递推
    第i-1天持有 dp[i-1][0]
    第i-1天未持有 -prices[i]
    dp[i][1] 递推
    第i-1天持有 prices[i]+dp[i-1][0]
    第i-1天未持有 dp[i-1][1]
dp[i][0] = max(dp[i - 1][0], -prices[i]);
dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0]);
  • 买卖股票的最佳时机II 买卖多次
    dp[i][0] 第i天持有股票
    dp[i][1] 第i天未持有股票
    dp[i][0] 递推
    第i-1天持有 dp[i-1][0]
    第i-1天未持有 dp[i-1][1]-prices[i]
    dp[i][1] 递推
    第i-1天持有 prices[i]+dp[i-1][0]
    第i-1天未持有 dp[i-1][1]
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]); 
dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + prices[i]);
  • 买卖股票的最佳时机III 可以完成两笔交易。
    五个状态,不是某一天一定要买入卖出,表示一个状态,这种状态是可以延续的!
    没有操作
    第一次买入
    第一次卖出
    第二次买入
    第二次卖出
    dp[i][1]递推
    第i天买入股票 dp[i][1] = dp[i-1][0] - prices[i]
    第i天没有操作 dp[i][1] = dp[i - 1][1]
    dp[i][2]递推
    第i天卖出股票 dp[i][2] = dp[i-1][1] + prices[i]
    第i天没有操作 dp[i][2] = dp[i - 1][2]
    dp[i][3]递推
    第i天买入股票了 dp[i][3] = dp[i-1][2] - prices[i]
    第i天没有操作 dp[i][3] = dp[i - 1][3]
    dp[i][4]递推
    第i天卖出股票 dp[i][4] = dp[i-1][3] + prices[i]
    第i天没有操作 dp[i][4] = dp[i - 1][4]
dp[i][0] = dp[i - 1][0];
dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
dp[i][2] = max(dp[i - 1][2], dp[i - 1][1] + prices[i]);
dp[i][3] = max(dp[i - 1][3], dp[i - 1][2] - prices[i]);
dp[i][4] = max(dp[i - 1][4], dp[i - 1][3] + prices[i]);

求子序列长度 dp数组的定义以及递推方法

  • 最长递增子序列
    dp[i]表示以包含nums[i]结尾的序列最长上升子序列的长度
    dp[i]是之前所有的进行比较 取最大的 dp[j] + 1
    if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1);

  • 最长连续递增序列 限制了递推方式
    if (nums[i + 1] > nums[i]) dp[i + 1] = dp[i] + 1;

  • 最长重复子数组 多维DP
    dp[i][j] :以下标i - 1为结尾的A,和以下标j - 1为结尾的B,最长重复子数组长度为dp[i][j]
    递推公式
    if (A[i - 1] == B[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1; // 连续

  • 最长公共子序列 不需要连续
    if (text1[i - 1] == text2[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;
    else dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值