【算法专题】动态规划之简单多状态 dp 问题

动态规划3.0

  • 动态规划 - - - 简单多状态 dp 问题
    • 1. 按摩师(打家劫舍Ⅰ的变形)
    • 2. 打家劫舍Ⅱ
    • 3. 删除并获得点数
    • 4. 粉刷房子
    • 5. 买卖股票的最佳时机含冷冻期
    • 6. 买卖股票的最佳时机含手续费
    • 7. 买卖股票的最佳时机Ⅲ
    • 8. 买卖股票的最佳时机Ⅳ

动态规划 - - - 简单多状态 dp 问题

1. 按摩师(打家劫舍Ⅰ的变形)

题目链接 -> Leetcode -面试题 17.16.按摩师

Leetcode -面试题 17.16.按摩师

题目:一个有名的按摩师会收到源源不断的预约请求,每个预约都可以选择接或不接。
在每次预约服务之间要有休息时间,因此她不能接受相邻的预约。给定一个预约请求序列,替按摩师找到最优的预约集合(总预约时间最长),返回总的分钟数。

注意:本题相对原题稍作改动

示例 1:
输入:[1, 2, 3, 1]
输出: 4
解释: 选择 1 号预约和 3 号预约,总时长 = 1 + 3 = 4。

示例 2:
输入:[2, 7, 9, 3, 1]
输出: 12
解释: 选择 1 号预约、 3 号预约和 5 号预约,总时长 = 2 + 9 + 1 = 12。

示例 3:
输入:[2, 1, 4, 5, 3, 1, 1, 3]
输出: 12
解释: 选择 1 号预约、 3 号预约、 5 号预约和 8 号预约,总时长 = 2 + 4 + 3 + 3 = 12。

思路:

  1. 状态表示:假设我们以以前的经验用 dp[i] 表示:选择到 i 位置时,此时的最长预约时长;但是我们这个题在 i 位置的时候,会面临「选择」或者「不选择」两种抉择,所依赖的状态需要细分:
  • f[i] 表示:选择到 i 位置时, nums[i] 必选,此时的最长预约时长;
  • g[i] 表示:选择到 i 位置时, nums[i] 不选,此时的最长预约时长;
  1. 状态转移方程:因为状态表示定义了两个,因此我们的状态转移方程也要分析两个:
  • 对于 f[i] :如果 nums[i] 必选,那么我们仅需知道 i - 1 位置在不选的情况下的最长预约时长,然后加上 nums[i] 即可,因此 f[i] = g[i - 1] + nums[i] ;
  • 对于 g[i] :如果 nums[i] 不选,那么 i - 1 位置上选或者不选都可以。因此,我们需要知道 i - 1 位置上选或者不选两种情况下的最长时长,因此 g[i] = max(f[i - 1], g[i - 1]);
  1. 返回值:根据状态表示,应该返回 max(f[n - 1], g[n - 1]) ;

代码如下:

		class Solution {public:int massage(vector<int>& nums) {int n = nums.size();if(n == 0) return 0;// f[i] 表示:选择到 i 位置时, nums[i] 必选,此时的最长预约时长// g[i] 表示:选择到 i 位置时, nums[i] 不选,此时的最长预约时长vector<int> f(n), g(n);f[0] = nums[0], g[0] = 0;for(int i = 1; i < n; i++){f[i] = g[i - 1] + nums[i];g[i] = max(f[i - 1], g[i - 1]);}return max(f[n - 1], g[n - 1]);}};

2. 打家劫舍Ⅱ

题目链接 -> Leetcode -213.打家劫舍Ⅱ

Leetcode -213.打家劫舍Ⅱ

题目:你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。
这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。
同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。

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

示例 1:
输入:nums = [2, 3, 2]
输出:3
解释:你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。

示例 2:
输入:nums = [1, 2, 3, 1]
输出:4
解释:你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。

示例 3:
输入:nums = [1, 2, 3]
输出:3

提示:

  • 1 <= nums.length <= 100
  • 0 <= nums[i] <= 1000

思路:这一个问题是「打家劫舍I」问题的变形。上一个问题是一个「单排」的模式,这一个问题是一个「环形」的模式,也就是首尾是相连的。但
是我们可以将「环形」问题转化为「两个单排」问题:

  1. 偷第一个房屋时的最大金额 x ,此时不能偷最后一个房子,因此就是偷 [0, n - 2] 区间的房子;
  2. 不偷第一个房屋时的最大金额 y ,此时可以偷最后一个房子,因此就是偷 [1, n - 1] 区间的房子;

两种情况下的「最大值」,就是最终的结果。因此,问题就转化成求「两次单排结果的最大值」

代码如下:

		class Solution {public:int rob1(vector<int>& nums){int n = nums.size();vector<int> f(n), g(n);f[0] = nums[0], g[0] = 0;for(int i = 1; i < n; i++){g[i] = max(g[i - 1], f[i - 1]);f[i] = g[i - 1] + nums[i];}return max(g[n - 1], f[n - 1]);}int rob(vector<int>& nums) {if(nums.size() == 1) return nums[0];// 去除最后一个元素,做一次打家劫舍Ⅰvector<int> arr1(nums.begin() + 1, nums.end());// 去除第一个元素,做一次打家劫舍Ⅰvector<int> arr2(nums.begin(), nums.end() - 1);// 取这两个的最大值即可return max(rob1(arr1), rob1(arr2));}};

3. 删除并获得点数

题目链接 -> Leetcode -740.删除并获得点数

Leetcode -740.删除并获得点数

题目:给你一个整数数组 nums ,你可以对它进行一些操作。
每次操作中,选择任意一个 nums[i] ,删除它并获得 nums[i] 的点数。之后,你必须删除 所有 等于 nums[i] - 1 和 nums[i] + 1 的元素。
开始你拥有 0 个点数。返回你能通过这些操作获得的最大点数。

示例 1:
输入:nums = [3, 4, 2]
输出:6
解释:
删除 4 获得 4 个点数,因此 3 也被删除。
之后,删除 2 获得 2 个点数。总共获得 6 个点数。

示例 2:
输入:nums = [2, 2, 3, 3, 3, 4]
输出:9
解释:
删除 3 获得 3 个点数,接着要删除两个 2 和 4 。
之后,再次删除 3 获得 3 个点数,再次删除 3 获得 3 个点数。
总共获得 9 个点数。

提示:

  • 1 <= nums.length <= 2 * 10^4
  • 1 <= nums[i] <= 10^4

思路:其实这道题依旧是「打家劫舍Ⅰ」问题的变型。我们注意到题目描述,选择 x 数字的时候, x - 1 与 x + 1 是不能被选择的,像「打家劫舍」问题中,选择 i 位置的金额之后,就不能选择 i - 1 位置以及 i + 1 位置的金额。

因此,我们可以创建一个大小为 10001 (根据题目的数据范围)的 hash 数组,将 nums 数组中每一个元素 x ,累加到 hash 数组下标为 x 的位置处,然后在 hash 数组上来一次「打家劫舍」即可。

代码如下:

		class Solution {public:int deleteAndEarn(vector<int>& nums) {const int N = 10001;int hash[N] = {0};for(const auto& e : nums) hash[e] += e;vector<int> f(N), g(N);for(int i = 1; i < N; i++){f[i] = g[i - 1] + hash[i];g[i] = max(g[i - 1], f[i - 1]);}return max(f[N - 1], g[N - 1]);}};

4. 粉刷房子

题目链接 -> Leetcode -LCR 091.粉刷房子

Leetcode -LCR 091.粉刷房子

题目:假如有一排房子,共 n 个,每个房子可以被粉刷成红色、蓝色或者绿色这三种颜色中的一种,你需要粉刷所有的房子并且使其相邻的两个房子颜色不能相同。
当然,因为市场上不同颜色油漆的价格不同,所以房子粉刷成不同颜色的花费成本也是不同的。每个房子粉刷成不同颜色的花费是以一个 n x 3 的正整数矩阵 costs 来表示的。
例如,costs[0][0] 表示第 0 号房子粉刷成红色的成本花费;costs[1][2] 表示第 1 号房子粉刷成绿色的花费,以此类推。
请计算出粉刷完所有房子最少的花费成本。

示例 1:
输入 : costs = [[17, 2, 17], [16, 16, 5], [14, 3, 19]]
输出 : 10
解释 : 将 0 号房子粉刷成蓝色,1 号房子粉刷成绿色,2 号房子粉刷成蓝色。
最少花费 : 2 + 5 + 3 = 10。

示例 2:
输入 : costs = [[7, 6, 2]]
输出 : 2

提示 :

  • costs.length == n
  • costs[i].length == 3
  • 1 <= n <= 100
  • 1 <= costs[i][j] <= 20

思路:

  1. 状态表示:我们这个题在 i 位置的时候,会面临「红」「蓝」「绿」三种抉择,所依赖的状态需要细分:
  • dp[i][0] 表示:粉刷到 i 位置的时候,最后一个位置粉刷上「红色」,此时的最小花费;
  • dp[i][1] 表示:粉刷到 i 位置的时候,最后一个位置粉刷上「蓝色」,此时的最小花费;
  • dp[i][2] 表示:粉刷到 i 位置的时候,最后一个位置粉刷上「绿色」,此时的最小花费
  1. 状态转移方程:因为状态表示定义了三个,因此我们的状态转移方程也要分析三个:
  • 对于 dp[i][0] :如果第 i 个位置粉刷上「红色」,那么 i - 1 位置上可以是「蓝色」或者「绿色」。因此我们需要知道粉刷到 i - 1 位置上的时候,粉刷上「蓝色」或者「绿色」的最小花费,然后加上 i 位置的花费即可。于是状态转移方程为: dp[i][0] = min(dp[i - 1][1], dp[i - 1][2]) + costs[i - 1][0] ;

同理,我们可以推导出另外两个状态转移方程为:

  • dp[i][1] = min(dp[i - 1][0], dp[i - 1][2]) + costs[i - 1][1] ;
  • dp[i][2] = min(dp[i - 1][0], dp[i - 1][1]) + costs[i - 1][2]
  1. 返回值:根据「状态表示」,应该返回最后一个位置粉刷上三种颜色情况下的最小值,因此需要返回:min(dp[n][0], min(dp[n][1], dp[n][2]))

代码如下:

		class Solution {public:int minCost(vector<vector<int>>& costs) {int n = costs.size();// f[i] 表示这一次选红色 // g[i] 表示这一次选蓝色// h[i] 表示这一次选绿色vector<int> f(n), g(n), h(n);f[0] = costs[0][0], g[0] = costs[0][1], h[0] = costs[0][2];for(int i = 1; i < n; i++){f[i] = min(g[i - 1], h[i - 1]) + costs[i][0];g[i] = min(f[i - 1], h[i - 1]) + costs[i][1];h[i] = min(f[i - 1], g[i - 1]) + costs[i][2];}return min(min(f[n - 1], g[n - 1]), h[n - 1]); }};

5. 买卖股票的最佳时机含冷冻期

题目链接 -> Leetcode -309.买卖股票的最佳时机含冷冻期

Leetcode -309.买卖股票的最佳时机含冷冻期

题目:给定一个整数数组prices,其中第 prices[i] 表示第 i 天的股票价格 。​

设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票) :

卖出股票后,你无法在第二天买入股票(即冷冻期为 1 天)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

示例 1:
输入: prices = [1, 2, 3, 0, 2]
输出 : 3
解释 : 对应的交易状态为 : [买入, 卖出, 冷冻期, 买入, 卖出]

示例 2 :
输入 : prices = [1]
输出 : 0

提示:

  • 1 <= prices.length <= 5000
  • 0 <= prices[i] <= 1000

思路:

  1. 状态表示:由于有「买入」「可交易」「冷冻期」三个状态,因此我们可以选择用三个数组,其中:
  • dp[i][0] 表示:第 i 天结束后,处于「买入」状态,此时的最大利润;
  • dp[i][1] 表示:第 i 天结束后,处于「可交易」状态,此时的最大利润;
  • dp[i][2] 表示:第 i 天结束后,处于「冷冻期」状态,此时的最大利润
  1. 状态转移方程:我们要谨记规则:
    i. 处于「买入」状态的时候,我们现在有股票,此时不能买股票,只能继续持有股票,或者卖出股票;
    ii. 处于「卖出」状态的时候:如果「在冷冻期」,不能买入;如果「不在冷冻期」,才能买入;
  • 对于 dp[i][0] ,我们有「两种情况」能到达这个状态:
    i. 在 i - 1 天持有股票,此时最大收益应该和 i - 1 天的保持一致: dp[i - 1][0] ;
    ii. 在 i 天买入股票,那我们应该选择 i - 1 天不在冷冻期的时候买入,由于买入需要花钱,所以此时最大收益为: dp[i - 1][1] - prices[i];

两种情况应取最大值,因此: dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]) 。

  • 对于 dp[i][1] ,我们有「两种情况」能到达这个状态:
    i. 在 i - 1 天的时候,已经处于冷冻期,然后啥也不干到第 i 天,此时对应的状态为:dp[i - 1][2] ;
    ii. 在 i - 1 天的时候,手上没有股票,也不在冷冻期,但是依旧啥也不干到第 i 天,此时对应的状态为 dp[i - 1][1] ;

两种情况应取最大值,因此: dp[i][1] = max(dp[i - 1][1], dp[i - 1][2]);

  • 对于 dp[1][i] ,我们只有「一种情况」能到达这个状态:
    i. 在 i - 1 天的时候,卖出股票。

因此对应的状态转移为: dp[i][2] = dp[i - 1][0] + prices[i]

  1. 初始化:三种状态都会用到前一个位置的值,因此需要初始化每一行的第一个位置:
  • dp[0][0] :此时要想处于「买入」状态,必须把第一天的股票买了,因此 dp[0][0] = -prices[0] ;
  • dp[0][1] :啥也不用干即可,因此 dp[0][1] = 0 ;
  • dp[0][2] :手上没有股票,买一下卖一下就处于冷冻期,此时收益为 0 ,因此 dp[0][2] = 0;
  1. 返回值:应该返回「卖出状态」下的最大值,因此应该返回 max(dp[n - 1][1], dp[n - 1][2]) ;

代码如下:

		class Solution {public:int maxProfit(vector<int>& prices) {int n = prices.size();// dp[i][0] 表示第 i 天处于买入状态// dp[i][1] 表示第 i 天处于卖出状态// dp[i][2] 表示第 i 天处于冷冻期vector<vector<int>> dp(n, vector<int>(3));dp[0][0] = -prices[0];for(int i = 1; i < n; i++){dp[i][0] = max(dp[i - 1][1] - prices[i], dp[i - 1][0]);dp[i][1] = max(dp[i - 1][2], dp[i - 1][1]);dp[i][2] = dp[i - 1][0] + prices[i];}return max(dp[n - 1][1], dp[n - 1][2]);}};

6. 买卖股票的最佳时机含手续费

题目链接 -> Leetcode -714.买卖股票的最佳时机含手续费

Leetcode -714.买卖股票的最佳时机含手续费

题目:给定一个整数数组 prices,其中 prices[i]表示第 i 天的股票价格 ;整数 fee 代表了交易股票的手续费用。

你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。

返回获得利润的最大值。

注意:这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。

示例 1:
输入:prices = [1, 3, 2, 8, 4, 9], fee = 2
输出:8
解释:能够达到的最大利润 :
在此处买入 prices[0] = 1
在此处卖出 prices[3] = 8
在此处买入 prices[4] = 4
在此处卖出 prices[5] = 9
总利润 : ((8 - 1) - 2) + ((9 - 4) - 2) = 8

示例 2:
输入:prices = [1, 3, 7, 5, 10, 3], fee = 3
输出:6

提示:

  • 1 <= prices.length <= 5 * 10^4
  • 1 <= prices[i] < 5 * 10^4
  • 0 <= fee < 5 * 10^4

思路:

  1. 状态表示:由于有「买入」「可交易」两个状态,因此我们可以选择用两个数组,其中:
  • f[i] 表示:第 i 天结束后,处于「买入」状态,此时的最大利润;
  • g[i] 表示:第 i 天结束后,处于「卖出」状态,此时的最大利润;
  1. 状态转移方程:我们选择在「卖出」的时候,⽀付这个手续费,那么在「买入」的时候,就不用再考虑手续费的问题。
  • 对于 f[i] ,我们有两种情况能到达这个状态:
    i. 在 i - 1 天「持有」股票,第 i 天啥也不干;此时最大收益为 f[i - 1] ;
    ii. 在 i - 1 天的时候「没有」股票,在第 i 天买入股票;此时最大收益为 g[i - 1] - prices[i]) ;

两种情况下应该取最大值,因此 f[i] = max(f[i - 1], g[i - 1] - prices[i]) ;

  • 对于 g[i] ,我们也有两种情况能够到达这个状态:
    i. 在 i - 1 天「持有」股票,但是在第 i 天将股票卖出;此时最大收益为: f[i - 1] + prices[i] - fee) ,记得手续费;
    ii. 在 i - 1 天「没有」股票,然后第 i 天啥也不干;此时最大收益为: g[i - 1] ;

两种情况下应该取最大值,因此 g[i] = max(g[i - 1], f[i - 1] + prices[i] - fee);

  1. 初始化:由于需要用到前面的状态,因此需要初始化第一个位置。
  • 对于 f[0] ,此时处于「买入」状态,因此 f[0] = -prices[0] ;
  • 对于 g[0] ,此时处于「没有股票」状态,啥也不干即可获得最大收益,因此 g[0] = 0
  1. 返回值:应该返回「卖出」状态下,最后一天的最大值收益: g[n - 1];

代码如下:

		class Solution {public:int maxProfit(vector<int>& prices, int fee){int n = prices.size();vector<vector<int>> dp(n, vector<int>(2));dp[0][0] = -prices[0];// dp[i][0] 表⽰:第 i 天结束后,处于「买⼊」状态,此时的最⼤利润// dp[i][1] 表⽰:第 i 天结束后,处于「卖出」状态,此时的最⼤利润for (int i = 1; i < n; i++){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] - fee);}return dp[n - 1][1];}};

7. 买卖股票的最佳时机Ⅲ

题目链接 -> Leetcode -123.买卖股票的最佳时机Ⅲ

Leetcode -123.买卖股票的最佳时机Ⅲ

题目:给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

示例 1:
输入:prices = [3, 3, 5, 0, 0, 3, 1, 4]
输出:6
解释:在第 4 天(股票价格 = 0)的时候买入,在第 6 天(股票价格 = 3)的时候卖出,这笔交易所能获得利润 = 3 - 0 = 3 。
随后,在第 7 天(股票价格 = 1)的时候买入,在第 8 天 (股票价格 = 4)的时候卖出,这笔交易所能获得利润 = 4 - 1 = 3 。

示例 2:
输入:prices = [1, 2, 3, 4, 5]
输出:4
解释:在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4 。
注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。
因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。

示例 3:
输入:prices = [7, 6, 4, 3, 1]
输出:0
解释:在这个情况下, 没有交易完成, 所以最大利润为 0。

示例 4:
输入:prices = [1]
输出:0

提示:

  • 1 <= prices.length <= 10^5
  • 0 <= prices[i] <= 10^5

思路:

  1. 状态表示:由于有「买入」「可交易」两个状态,因此我们可以选择用两个数组。但是这道题里面还有交易次数的限制,因此我们还需要再加上一维,用来表示交易次数。其中:
  • f[i][j] 表示:第 i 天结束后,完成了 j 次交易,处于「买入」状态,此时的最大利润;
  • g[i][j] 表示:第 i 天结束后,完成了 j 次交易,处于「卖出」状态,此时的最大利润。
  1. 状态转移方程:
  • 对于 f[i][j] ,我们有两种情况到这个状态:
    i. 在 i - 1 天的时候,交易了 j 次,处于「买入」状态,第 i 天啥也不干即可。此时最大利润为: f[i - 1][j] ;
    ii. 在 i - 1 天的时候,交易了 j 次,处于「卖出」状态,第 i 天的时候把股票买了。此时的最大利润为: g[i - 1][j] - prices[i] ;

综上,我们要的是「最大利润」,因此是两者的最大值: f[i][j] = max(f[i - 1][j], g[i - 1][j] - prices[i]) ;

  • 对于 g[i][j] ,我们也有两种情况可以到达这个状态:
    i. 在 i - 1 天的时候,交易了 j 次,处于「卖出」状态,第 i 天啥也不干即可。此时的最大利润为: g[i - 1][j] ;
    ii. 在 i - 1 天的时候,交易了 j - 1 次,处于「买入」状态,第 i 天把股票卖了,然后就完成了 j 笔交易。此时的最大利润为: f[i - 1][j - 1] + prices[i] 。但是这个状态不⼀定存在,要先判断⼀下;

综上,我们要的是最大利润,因此状态转移方程为:
g[i][j] = g[i - 1][j];
if(j >= 1) g[i][j] = max(g[i][j], f[i - 1][j - 1] + prices[i]);

  1. 初始化:由于需要用到 i = 0 时的状态,因此我们初始化第一行即可。
  • 当处于第 0 天的时候,只能处于「买入过一次」的状态,此时的收益为 -prices[0] ,因此 f[0][0] = - prices[0] ;
  • 为了取 max 的时候,一些不存在的状态「起不到干扰」的作用,我们统统将它们初始化为 -INF (用 INT_MIN 在计算过程中会有「溢出」的风险,这里 INF 折半取 0x3f3f3f3f ,足够小即可)
  1. 返回值:返回处于「卖出状态」的最大值,但是我们也「不知道是交易了几次」,因此返回 g 表最后一行的最大值。

代码如下:

		class Solution {public:int maxProfit(vector<int>& prices){const int INF = 0x3f3f3f3f; // 整型最大的一半,足够大int n = prices.size();// 为了防止不存在的状态影响 max 的取值,初始化为整型最大值的一半的负数,即足够小vector<vector<int>> f(n, vector<int>(3, -INF));vector<vector<int>> g(n, vector<int>(3, -INF));f[0][0] = -prices[0], g[0][0] = 0;// f[i][j] 表示:第 i 天结束后,完成了 j 次交易,处于「买⼊」状态,此时的最⼤利润;// g[i][j] 表示:第 i 天结束后,完成了 j 次交易,处于「卖出」状态,此时的最⼤利润。for (int i = 1; i < n; i++){for (int j = 0; j < 3; j++){f[i][j] = max(f[i - 1][j], g[i - 1][j] - prices[i]);if (j >= 1) g[i][j] = max(g[i - 1][j], f[i - 1][j - 1] + prices[i]);else g[i][j] = g[i - 1][j];}}int ret = 0;for (int i = 0; i < 3; i++) ret = max(ret, g[n - 1][i]);return ret;}};

8. 买卖股票的最佳时机Ⅳ

题目链接 -> Leetcode -188.买卖股票的最佳时机Ⅳ

Leetcode -188.买卖股票的最佳时机Ⅳ

题目:给你一个整数数组 prices 和一个整数 k ,其中 prices[i] 是某支给定的股票在第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。也就是说,你最多可以买 k 次,卖 k 次。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

示例 1:
输入:k = 2, prices = [2, 4, 1]
输出:2
解释:在第 1 天(股票价格 = 2) 的时候买入,在第 2 天(股票价格 = 4) 的时候卖出,这笔交易所能获得利润 = 4 - 2 = 2 。

示例 2:
输入:k = 2, prices = [3, 2, 6, 5, 0, 3]
输出:7
解释:在第 2 天(股票价格 = 2) 的时候买入,在第 3 天(股票价格 = 6) 的时候卖出, 这笔交易所能获得利润 = 6 - 2 = 4 。
随后,在第 5 天(股票价格 = 0) 的时候买入,在第 6 天(股票价格 = 3) 的时候卖出, 这笔交易所能获得利润 = 3 - 0 = 3 。

提示:

  • 1 <= k <= 100
  • 1 <= prices.length <= 1000
  • 0 <= prices[i] <= 1000

思路:本题思路与上题思路类似,只是上题最多可以交易两次,本题是可以交易 k 次,所以在代码上稍作修改即可;

代码如下:

		class Solution {public:int maxProfit(int k, vector<int>& prices){const int INF = 0x3f3f3f3f;int n = prices.size();vector<vector<int>> f(n, vector<int>(k + 1, -INF)), g(n, vector<int>(k + 1, -INF));f[0][0] = -prices[0], g[0][0] = 0;// f[i][j] 表示第 i 天结束后,进行了 j 次交易,此时处于买入状态,此时的最大利益// g[i][j] 表示第 i 天结束后,进行了 j 次交易,此时处于卖出状态,此时的最大利益   for (int i = 1; i < n; i++){for (int j = 0; j < k + 1; j++){f[i][j] = max(f[i - 1][j], g[i - 1][j] - prices[i]);if (j >= 1) g[i][j] = max(g[i - 1][j], f[i - 1][j - 1] + prices[i]);else g[i][j] = g[i - 1][j];}}int ret = 0;for (int i = 0; i < k + 1; i++) ret = max(ret, g[n - 1][i]);return ret;}};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/645188.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Java 设计模式】行为型之备忘录模式

文章目录 1. 定义2. 应用场景3. 代码实现结语 备忘录模式&#xff08;Memento Pattern&#xff09;是一种行为型设计模式&#xff0c;用于捕获一个对象的内部状态&#xff0c;以便稍后可以将该对象恢复到此状态。备忘录模式允许在不破坏封装性的前提下捕获和外部化对象的内部状…

Could not autowire. No beans of ‘RedisConnectionFactory‘ type found.已解决

springboot2.7.8 redis3.2.100 在springboot中 使用RedisConnectionFactory 出现这样的错误Could not autowire. No beans of ‘RedisConnectionFactory‘ type found. 只需要在pom.xml中加入 <!-- 整合redis --> <dependency> <groupId>org.springf…

客户端请求+返回 服务端之间的请求和返回 实现rpc通信

背景&#xff1a; 1.无论什么类型的游戏&#xff0c;我们都会有rpc通信的需求。 2.由于客户端直连的是游戏服&#xff0c;如果工会&#xff0c;匹配之类的服务是单独的服务的话&#xff0c;必然要进行游戏服到业务服之间的转发&#xff0c;我们是否需要再转发时单独定义Req和Re…

Halcon基于透视形变的模板匹配

Halcon基于透视形变的模板匹配 透视形变也是一种形变&#xff0c;属于形状模板匹配的延伸。形状模板匹配对于形变非常敏感&#xff0c;而透视形变匹配则能适应出现透视形变的情况。透视形变的匹配又分为无标定和有标定两种情况。基于透视形变的匹配步骤如下。 &#xff08;1&a…

HTTP动态代理的原理及其对网络性能的影响

HTTP动态代理是一种通过代理服务器来转发HTTP请求和响应数据的网络技术&#xff0c;它可以优化网络性能、提高网络安全性&#xff0c;并解决跨域请求的问题。本文将详细介绍HTTP动态代理的原理及其对网络性能的影响。 一、HTTP动态代理的原理 HTTP动态代理的基本原理是在客户…

【数据结构四】栈与Stack详解

目录 栈与Stack 1.实现一个自己的栈 2.Stack的基本使用 3.栈的一些oj题训练 4.栈&#xff0c;虚拟机栈&#xff0c;栈帧的区别 栈与Stack 栈 &#xff1a;一种特殊的线性表&#xff0c;其 只允许在固定的一端进行插入和删除元素操作 。进行数据插入和删除操作的一端称为栈顶…

opencv#34 边缘检测(二)

Laplacian(拉普拉斯)算子 前面介绍的Sobel算子和Scharr算子存在的问题: 1.要分别计算两个方向&#xff08;x,y)的边缘&#xff0c;之后将两方向的边缘进行叠加。 2.边缘与方向相关性较大。当我们通过Sobel算子提取x方向检测时&#xff0c;它所能够检测到的边缘都是一个沿着y…

大数据分析组件Hive-集合数据结构

Hive的数据结构 前言一、array数组类型二、map键值对集合类型三、struct结构体类型 前言 Hive是一个基于Hadoop的数据仓库基础设施&#xff0c;用于处理大规模分布式数据集。它提供了一个类似于SQL的查询语言&#xff08;称为HiveQL&#xff09;&#xff0c;允许用户以类似于关…

差分进化算法求解基于移动边缘计算 (MEC) 的无线区块链网络的联合挖矿决策和资源分配(提供MATLAB代码)

一、优化模型介绍 在所研究的区块链网络中&#xff0c;优化的变量为&#xff1a;挖矿决策&#xff08;即 m&#xff09;和资源分配&#xff08;即 p 和 f&#xff09;&#xff0c;目标函数是使所有矿工的总利润最大化。问题可以表述为&#xff1a; max ⁡ m , p , f F miner …

gin中使用限流中间件

限流又称为流量控制&#xff08;流控&#xff09;&#xff0c;通常是指限制到达系统的并发请求数&#xff0c;本文列举了常见的限流策略&#xff0c;并以gin框架为例演示了如何为项目添加限流组件。 限流 限流又称为流量控制&#xff08;流控&#xff09;&#xff0c;通常是指…

如何在美国硅谷高防服务器上运行自定义的脚本和应用程序

在美国硅谷高防服务器上运行自定义的脚本和应用程序需要一定的技术和知识。下面我们将介绍一些关键步骤&#xff0c;帮助您顺利地在这些服务器上运行自定义应用程序和脚本。 确保您有对服务器的访问权限&#xff0c;并且已经通过SSH等方式连接到服务器。接下来&#xff0c;您可…

本科毕业设计过程中应该锻炼的能力 (深度学习方向)

摘要: 本文以本科毕业设计做深度学习方向, 特别是全波形反演为例, 描述学生应在此过程中锻炼的能力. 搭建环境的能力. 包括 Python, PyTorch 等环境的安装.采集数据的能力. 包括 OpenFWI 等数据集.查阅资料的能力. 包括自己主要参考的文献, 以及其它相关文献 (不少于 20 篇). …

统信系统申威cpu 部署mysql、 portainer、node-exporter、Prometheus、AlertManager、grafana

mysql容器部署 MySQL 是一款广泛使用的开源关系型数据库管理系统&#xff0c;用于存储、管理和检索结构化数据&#xff0c;并通过 SQL 语言支持高效率的数据操作和管理。 docker run --privileged -itd --name mysql_8 \ -e MYSQL_USER"admin" -e MYSQL_PASSWORD&…

基于决策融合的极限学习机分类预测,基于融合ELM的分类预测,基于融合极限学习机的电子鼻采集数据分类

目录 背影 极限学习机 基于决策融合的极限学习机分类预测,基于融合ELM的分类预测,基于融合极限学习机的电子鼻采集数据分类 主要参数 MATLAB代码 效果图 结果分析 展望 完整代码下载链接:基于决策融合的极限学习机分类预测,基于融合ELM的分类预测,基于融合极限学习机的电…

不就业,纯兴趣,应该自学C#还是JAVA?

不就业&#xff0c;纯兴趣&#xff0c;应该自学C#还是JAVA? 在开始前我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「JAVA的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给大家&#xff…

微信小程序(十四)分包和分包预加载

注释很详细&#xff0c;直接上代码 新增内容&#xff1a; 1.分包的配置 2.分包预加载的写法 先说说为什么需要分包&#xff1a; 小程序追求小而快&#xff0c;主包的大小控制是小程序上线的硬性要求&#xff0c;分包有利于小程序优化加载速度 分包的注意事项&#xff1a; 单个分…

网络原理-初识(1)

目录 网络发展史 独立模式 网络互连 局域网LAN 广域网WAN 网络通信基础 IP地址 概念 格式 端口 概念 格式 认识协议 概念 作用 五元组 网络发展史 独立模式 独立模式:计算机之间相互独立; 网络互连 随着时代的发展,越来越需要计算机之间相互通信,共享软件和数…

【AI的未来 - AI Agent系列】【MetaGPT】6. 用ActionNode重写技术文档助手

文章目录 0. 前置推荐阅读1. 重写WriteDirectory Action1.1 实现WriteDirectory的ActionNode&#xff1a;DIRECTORY_WRITE1.2 将 DIRECTORY_WRITE 包进 WriteDirectory中 2. 重写WriteContent Action2.1 思考重写方案2.2 实现WriteContent的ActionNode2.3 改写WriteContent Act…

UV紫外激光打标机的优缺点是什么

​ UV紫外激光打标机具有以下优点&#xff1a; 1. 精度高&#xff1a;紫外激光打标机的光束质量好&#xff0c;聚焦光斑小&#xff0c;可以实现在各种材料上进行超精细打标。 2. 速度快&#xff1a;由于紫外激光的独特特性&#xff0c;打标速度非常快&#xff0c;提高了生产效…

MongoDB基本常用命令(一)

案例需求 存放文章评论的数据存放到MongoDB中&#xff0c;数据结构参考如下&#xff1a; 数据库&#xff1a;articledb 专栏文章评论comment字段名称字段含义字段类型备注_idIDObjectId或StringMongo的主键的字段articleid文章IDStringcontent评论内容Stringuserid评论人IDSt…