dp分类--状态枚举

一、股票系列

股票系列常见提问方式:

给定n天的股票价格,用户可以买入和卖出股票且交易次数(买入+卖出算一次交易)最多k次,计算可以获得的最大收益?

注意不同于取元素,每天的操作是不同的因此对应买入和卖出操作将元素状态分为有没有持股,所以元素状态必须作为dp状态;

1、最佳买卖股票时机含冷冻期 309

给定一个整数数组prices,其中第 prices[i] 表示第 i 天的股票价格 。​设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
示例 1:
输入: prices = [1,2,3,0,2]
输出: 3

元素状态必须作为dp状态,因此是二维dp数组dp[i][0]表示第i天不持股, dp[i][1]表示第i天持股。

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n = prices.size();
        vector<vector<int>> dp(n, vector<int>(2));
        dp[0][1] = -prices[0];
        for (int i = 1; i < n; ++i) {
            dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i]);
            //第二天持股最大收益:前两天买入的最大值,因为两天内无法交易两次
            if (i == 1) dp[i][1] = max(-prices[0], -prices[1]);
            else dp[i][1] = max(dp[i-1][1], dp[i-2][0] - prices[i]);
        }
        return dp[n-1][0];
    }
};

2、买卖股票的最佳时机含手续费 714

给定一个整数数组 prices,其中 prices[i]表示第 i 天的股票价格 ;整数 fee 代表了交易股票的手续费用。返回获得利润的最大值。
示例 1:
输入:prices = [1, 3, 2, 8, 4, 9], fee = 2
输出:8

将元素状态作为dp状态后,每次扣费可以在卖出股票时进行。这里dp[i][0]只依赖dp[i-1][0]和dp[i-1][1],所以省略第一维度:

class Solution {
public:
    int maxProfit(vector<int>& prices, int fee) {
        int n = prices.size();
        vector<int> dp(2);
        dp[1] = -prices[0];
        for (int i=1;i < n; ++i) {
            dp[0] = max(dp[0], dp[1] + prices[i] - fee); 
            dp[1] = max(dp[1], dp[0] - prices[i]);
        }
        return max(dp[0], dp[1]);
    }
};

3、买卖股票的最佳时机 III 123

给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。
示例 1:
输入:prices = [3,3,5,0,0,3,1,4]
输出:6

交易次数受限制,这个限制条件也必须作为dp状态;因此一共是三维dp数组:dp[i][j][0]表示第i天第i次不持股, dp[i][j][1]表示第i天第i次持股。
注意可以优化掉第一维,因为只依赖上一维度

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n = prices.size();
        //第几天/交易次数/有没有股
        //次数维度使用3,是为了表示的含义更明确
        vector<vector<vector<int>>> dp(n, vector<vector<int>>(3, vector<int>(2)));
        //初始化:考虑第一次持有的收益,第一次不持股的收益是0不用再赋值了
        dp[0][1][1] = -prices[0];
        dp[0][2][1] = -prices[0];

        for (int i = 1; i < n; ++i) {
            dp[i][1][0] = max(dp[i-1][1][0], dp[i-1][1][1] + prices[i]);
            dp[i][1][1] = max(dp[i-1][1][1], -prices[i]);//第一次交易的利润就是买入价格-prices[i]

            dp[i][2][0] = max(dp[i-1][2][0], dp[i-1][2][1] + prices[i]);
            dp[i][2][1] = max(dp[i-1][2][1], dp[i-1][1][0] - prices[i]);
        }
        return dp[n-1][2][0];
    }
};

4、 买卖股票的最佳时机 IV 188

给定一个整数数组 prices ,它的第 i 个元素 prices[i] 是一支给定的股票在第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。
示例 1:
输入:k = 2, prices = [2,4,1]
输出:2

参考上面示例

class Solution {
public:
    int maxProfit(int k, vector<int>& prices) {
        if (k == 0 || prices.empty()) {
            return 0;
        }
        int n = prices.size();
        //第几天/第几次/有没有股
        vector<vector<vector<int>>> dp(n, vector<vector<int>>(k+1, vector<int>(2)));
        for (int i=1;i <= k; ++i) {
            dp[0][i][1] = -prices[0];
        }

        for (int i = 1; i < n; i++) {
            for (int j = 1; j <= k; ++j) {
                dp[i][j][0] = max(dp[i-1][j][0], dp[i-1][j][1] + prices[i]);
                dp[i][j][1] = max(dp[i-1][j][1], dp[i-1][j-1][0]-prices[i]);//这里第一次持有收益是-prices[i],但是dp[0][0]刚好是0计算结果可以统一。
            }
        }
        return dp[n-1][k][0];
    }
};
动态规划背包问题分类与解决方案总结 背包问题是组合优化领域经典问题,核心目标为:给定容量限制的背包与若干带“消耗属性”(如重量、体积)和“价值属性”的物品,通过合理选物品使总价值最大化,且总消耗不超背包容量。按物品选取规则、约束条件可分为7类核心场景,以下从核心特点、数学模型、DP解法(含状态定义、转移方程)、优化技巧、适用场景展开详细总结。 一、0-1背包问题(基础核心) 1. 核心特点 每个物品仅能选或不选(二选一,无中间状态),不可重复选取、不可分割。 2. 数学模型 - 输入:n个物品,物品i的消耗(如重量) w_i 、价值 v_i ;背包最大容量 W 。 - 决策变量: x_i \in \{0,1\} ( x_i=1 选物品i, x_i=0 不选)。 - 目标函数:最大化总价值 \max \sum_{i=1}^n v_i x_i 。 - 约束条件:总消耗不超容量 \sum_{i=1}^n w_i x_i \leq W 。 3. DP解法 (1)二维DP(原始版,无空间优化) - 状态定义: dp[i][j] 表示前i个物品中选,背包容量为j时的最大价值。 - 转移方程: dp[i][j] = \begin{cases} dp[i-1][j] & (j < w_i,无法选物品i) \\ \max(dp[i-1][j], dp[i-1][j-w_i] + v_i) & (j \geq w_i,选/不选物品i取最大值) \end{cases} - 初始化: dp[0][j]=0 (前0个物品,价值恒为0)、 dp[i][0]=0 (容量为0,无法选物品,价值恒为0)。 - 时间复杂度: O(nW) ,空间复杂度: O(nW) 。 (2)一维DP(空间优化版,常用) - 核心逻辑:二维DPdp[i][j] 仅依赖 dp[i-1][\cdot] ,可压缩为一维数组,通过“逆序遍历容量”避免重复选同一物品。 - 状态定义: dp[j] 表示背包容量为j时的最大价值。 - 转移方程: dp[j] = \max(dp[j], dp[j-w_i] + v_i) \quad (j从W逆序遍历至w_i) - 初始化: dp[0]=0 ,其余 dp[j]=0 (或负无穷,视是否允许“空选”调整)。 - 时间复杂度: O(nW) ,空间复杂度: O(W) 。 4. 扩展:统计选法方案数 - 目标:统计“总消耗≤j且总价值达标”(或仅“总消耗=j”)的选法数量,不关注价值大小。 - 转移方程: dp[j] += dp[j-w_i] (j从W逆序遍历至w_i)。 - 初始化: dp[0]=1 (容量0时,“不选任何物品”为1种方案),其余 dp[j]=0 。 5. 适用场景 物品不可重复、数量少(n≤1000)、容量适中(W≤1e4),如“物品选/不选的价值最大化”。 二、完全背包问题 1. 核心特点 每个物品可无限次选取(无数量限制),仅受背包容量约束。 2. 数学模型 - 输入、目标函数、约束条件同0-1背包,仅决策变量调整: x_i \in \{0,1,2,...\} (非负整数,可重复选)。 3. DP解法 (1)二维DP(原始版) - 状态定义:同0-1背包 dp[i][j] (前i个物品、容量j的最大价值)。 - 转移方程: dp[i][j] = \begin{cases} dp[i-1][j] & (j < w_i,无法选物品i) \\ \max(dp[i-1][j], dp[i][j-w_i] + v_i) & (j \geq w_i,选物品i后可继续选同一物品) \end{cases} - 时间复杂度: O(nW) ,空间复杂度: O(nW) 。 (2)一维DP(空间优化版,常用) - 核心逻辑:允许重复选物品,故“正序遍历容量”(选当前物品后,后续容量可复用该物品)。 - 状态定义:同0-1背包一维版 dp[j] 。 - 转移方程: dp[j] = \max(dp[j], dp[j-w_i] + v_i) \quad (j从w_i正序遍历至W) - 初始化: dp[0]=0 ,其余 dp[j]=0 。 - 时间复杂度: O(nW) ,空间复杂度: O(W) 。 4. 数学优化(辅助技巧) 计算物品单位消耗价值 \frac{v_i}{w_i} ,优先选性价比高的物品,可快速得到近似最优解;但因容量可能无法整除消耗,需结合DP修正精确解。 5. 扩展:统计选法方案数 - 转移方程: dp[j] += dp[j-w_i] (j从w_i正序遍历至W)。 - 初始化: dp[0]=1 ,其余 dp[j]=0 。 6. 适用场景 物品可重复使用,如“硬币兑换(不限硬币数量,凑指定金额)”“材料无限,制作物品价值最大化”。 三、多重背包问题 1. 核心特点 每个物品有固定数量限制 c_i (最多选 c_i 个),介于0-1背包( c_i=1 )与完全背包( c_i→∞ )之间。 2. 数学模型 - 输入、目标函数、约束条件同前,决策变量调整: x_i \in \{0,1,...,c_i\} (选数≤物品上限)。 3. DP解法 (1)直接法(暴力枚举,不推荐) - 状态定义: dp[j] (容量j的最大价值)。 - 转移方程: dp[j] = \max(dp[j], dp[j-k \cdot w_i] + k \cdot v_i) \quad (0 \leq k \leq c_i,且j \geq k \cdot w_i) - 时间复杂度: O(nW \sum c_i) ,效率极低,仅适用于 c_i 极小场景。 (2)二进制优化(常用,高效) - 核心逻辑:将物品数量 c_i 拆分为 \log_2 c_i 个“新物品”(如 c_i=5 拆为1、2、2, c_i=7 拆为1、2、4),新物品重量=原重量×拆分系数,价值=原价值×拆分系数,转化为0-1背包求解(新物品选/不选对应原物品选对应数量)。 - 转移方程:复用0-1背包一维DP方程( dp[j] = \max(dp[j], dp[j-w_{新}] + v_{新}) ,j逆序遍历)。 - 时间复杂度: O(nW \log c_i) ,空间复杂度: O(W) ,适配多数场景。 (3)单调队列优化(极限优化,大数据量) - 核心逻辑:通过滑动窗口维护“容量j附近可选取的最优价值”,消除枚举k的冗余步骤,将时间复杂度降至线性。 - 转移方程推导:对容量j按 j \mod w_i = r 分组(r=0,1,...,w_i-1),每组内 j = r + t \cdot w_i ,方程转化为: dp[r + t \cdot w_i] = \max(dp[r + (t-k) \cdot w_i] + k \cdot v_i) \quad (0 \leq k \leq \min(c_i, t)) 用单调队列维护 dp[r + (t-k) \cdot w_i] - (t-k) \cdot v_i 的最大值,直接取队首更新当前dp值。 - 时间复杂度: O(nW) ,空间复杂度: O(W) ,适用于 W≥1e5 的大数据场景。 4. 扩展:统计选法方案数 - 二进制优化后复用0-1背包方案数方程,直接法复用枚举k的累加方程( dp[j] += dp[j-k \cdot w_i] )。 - 初始化: dp[0]=1 ,其余 dp[j]=0 。 5. 适用场景 物品有数量限制,如“商品限购、材料库存有限,选物品价值最大化”。 四、混合背包问题 1. 核心特点 同一问题中同时包含0-1背包物品(选1次)、完全背包物品(选无限次)、多重背包物品(选有限次),需分类型处理物品。 2. 解法核心 - 分类适配策略: 1. 0-1背包物品:直接用一维DP逆序遍历容量更新; 2. 完全背包物品:直接用一维DP正序遍历容量更新; 3. 多重背包物品:先二进制拆分(或单调队列优化),转化为0-1背包后逆序遍历更新。 - 状态定义: dp[j] (容量j的最大价值),全局共用一个DP数组。 - 转移方程:按物品类型复用对应背包的一维转移方程,遍历顺序为“先遍历物品→再按类型遍历容量”。 3. 时间复杂度 取决于各类物品占比,整体为 O(nW + n_多 W \log c_i) ( n_多 为多重背包物品数量),空间复杂度 O(W) 。 4. 适用场景 多类型物品共存,如“购物时部分商品限购、部分不限购、部分仅一件”。 五、分组背包问题 1. 核心特点 物品按类别分组,每组内至少选0个、最多选1个物品(组间独立,组内互斥)。 2. 数学模型 - 输入:m组物品,第k组含 s_k 个物品,组内物品i的消耗 w_{k,i} 、价值 v_{k,i} ;背包容量 W 。 - 决策变量:每组内仅1个物品 x_{k,i}=1 (或全为0),其余 x_{k,i}=0 。 - 目标函数: \max \sum_{k=1}^m \sum_{i=1}^{s_k} v_{k,i} x_{k,i} ,约束条件: \sum_{k=1}^m \sum_{i=1}^{s_k} w_{k,i} x_{k,i} \leq W 。 3. DP解法 - 核心逻辑:先遍历组→再逆序遍历容量(避免组内选多个物品)→最后遍历组内物品,按“选组内某物品/不选组内任何物品”更新DP- 状态定义: dp[j] (容量j的最大价值)。 - 转移方程: dp[j] = \max(dp[j], dp[j-w_{k,i}] + v_{k,i}) \quad (先遍历组k→j从W逆序至w_{k,i}→遍历组k内物品i) - 初始化: dp[0]=0 ,其余 dp[j]=0 。 - 时间复杂度: O(mW \bar{s}) ( \bar{s} 为每组平均物品数),空间复杂度 O(W) 。 4. 扩展:统计选法方案数 - 转移方程: dp[j] += dp[j-w_{k,i}] (遍历顺序同上,j逆序)。 - 初始化: dp[0]=1 ,其余 dp[j]=0 。 5. 适用场景 物品分组且组内互斥,如“考试选做题(每组1道,选1道或不选)、套餐选择(每组1个套餐,选1个或不选)”。 六、二维费用背包问题 1. 核心特点 物品需消耗两类资源(如重量+体积、重量+金额),背包对两类资源均有容量限制(如背包限重W、限体积V),分0-1型与完全型。 2. 数学模型(以0-1型为例) - 输入:n个物品,物品i消耗资源1 w_{i1} 、资源2 w_{i2} ,价值 v_i ;背包资源1容量 W 、资源2容量 V 。 - 目标函数: \max \sum_{i=1}^n v_i x_i ,约束条件: \sum_{i=1}^n w_{i1} x_i \leq W 、 \sum_{i=1}^n w_{i2} x_i \leq V , x_i \in \{0,1\} 。 3. DP解法 (1)二维DP(原始版) - 状态定义: dp[i][j][k] 表示前i个物品、资源1容量j、资源2容量k时的最大价值。 - 转移方程(0-1型): dp[i][j][k] = \begin{cases} dp[i-1][j][k] & (j < w_{i1} 或 k < w_{i2}) \\ \max(dp[i-1][j][k], dp[i-1][j-w_{i1}][k-w_{i2}] + v_i) & (j \geq w_{i1} 且 k \geq w_{i2}) \end{cases} - 时间复杂度: O(nWV) ,空间复杂度: O(nWV) 。 (2)二维滚动数组(空间优化版,常用) - 核心逻辑:压缩物品维度,用二维数组 dp[j][k] 表示资源1容量j、资源2容量k的最大价值,0-1型需“双逆序遍历资源”,完全型需“双正序遍历资源”。 - 转移方程(0-1型): dp[j][k] = \max(dp[j][k], dp[j-w_{i1}][k-w_{i2}] + v_i) \quad (j从W逆序至w_{i1},k从V逆序至w_{i2}) - 转移方程(完全型): dp[j][k] = \max(dp[j][k], dp[j-w_{i1}][k-w_{i2}] + v_i) \quad (j从w_{i1}正序至W,k从w_{i2}正序至V) - 初始化: dp[0][0]=0 ,其余 dp[j][k]=0 。 - 时间复杂度: O(nWV) ,空间复杂度: O(WV) 。 4. 适用场景 物品消耗多类资源,如“行李箱限重且限体积,选物品价值最大化”“项目限资金且限时间,选任务收益最大化”。 七、背包问题通用总结 1. 核心共性 - 本质:通过DP存储“局部容量下的最优价值/方案数”,避免重复计算,核心是“状态定义+转移逻辑”; - 空间优化优先级:二维DP→一维滚动数组(单/多资源维度),优先压缩物品维度; - 时间优化核心:减少冗余枚举(如二进制拆分消除k枚举、单调队列优化滑动窗口)。 2. 关键差异对比表 背包类型 物品选取规则 核心遍历顺序 核心优化技巧 时间复杂度 核心场景 0-1背包 选1次或不选 容量逆序 一维滚动数组   物品不可重复 完全背包 无限次选 容量正序 一维滚动数组   物品可重复使用 多重背包 最多选 次 逆序(拆分后) 二进制拆分、单调队列   物品有数量限制 混合背包 0-1+完全+多重共存 分类型定顺序 分类适配 适配多类型复杂度 多规则物品共存 分组背包 组内最多选1个 组→逆序容量→组内物品 组内遍历控制   物品分组且组内互斥 二维费用背包 消耗两类资源,选1次/无限次 双资源逆序/正序 二维滚动数组   物品消耗多类资源 3. 解题步骤 1. 明确物品类型(选法规则、消耗资源数)与背包约束(容量/资源上限); 2. 定义DP状态(单/多维度,对应资源容量,明确存储含义:价值/方案数); 3. 推导转移方程(按“选/不选当前物品”拆分,适配选取规则); 4. 确定初始化值(按“空容量/空物品”场景赋值,方案数必设 dp[0...]=1 ); 5. 优化空间/时间(优先一维数组,大数据量补二进制/单调队列优化); 6. 验证边界(如容量不足、物品数量为0的极端场景)。 通过以上分类与解法,可覆盖绝大多数背包问题场景,核心是掌握“选法规则→遍历顺序→优化技巧”的对应关系,灵活适配具体需求。 帮我补充修改
最新发布
11-26
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值