【算法】动态规划---多态dp问题

news2024/11/6 7:13:01

多态dp问题

  • 一.[leetcode] (打家劫舍I) 17.16.按摩师
  • 二. [leetcode] 213. 打家劫舍 II
  • 三.[leetcode] 740. 删除并获得点数
  • 四.[leetcode] LCR 091. 粉刷房子
  • 五.[leetcode] 309. 买卖股票的最佳时机含冷冻期
  • 六.[leetcode] 714. 买卖股票的最佳时机含手续费
  • 七.[leetcode] 123. 买卖股票的最佳时机 III
  • 八.[leetcode] 188. 买卖股票的最佳时机 IV

一.[leetcode] (打家劫舍I) 17.16.按摩师

  • 题目链接: 面试题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] 不选,此时的最长预约时长。
    2. 状态转移方程:
      因为状态表示定义了两个,因此我们的状态转移方程也要分析两个:

      • 对于 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])
    3. 初始化:

      • 可以在最前面加上一个「辅助结点」,帮助我们初始化。使用这种技巧要注意两个点:
        • i. 辅助结点里面的值要「保证后续填表是正确的」;
        • ii. 「下标的映射关系」。
      • 仅需初始化 f[1] = nums[0], g[0] = 0即可
    4. 填表顺序:
      从左向右, 两个表一起填

    5. 返回值:
      返回maax(f[n] , g[n])

  • 代码示例(Java):

class Solution {
    public int massage(int[] nums) {
        //1.创建dp表
        int n = nums.length;
        int[] f = new int[n+1];
        int[] g = new int[n+1];
        //2.初始化
        f[0] = g[0] = 0;
        //3.填表
        for(int i = 1;i<=n;i++){
            f[i] = g[i-1]+nums[i-1];
            g[i] = Math.max(f[i-1],g[i-1]);
        }
        //4.确定返回值.
        return Math.max(f[n],g[n]);
    }
}

在这里插入图片描述

二. [leetcode] 213. 打家劫舍 II

  • 题目链接: 213. 打家劫舍 II
  • 题目描述:
    你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。
    给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。

示例 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

  • 解法:(动态规划)
    这⼀个问题是「打家劫舍I」按摩师 问题的变形。上一个问题是一个「单排」的模式,这一个问题是一个「环形」的模式,也就是首尾是相连的。但是我们可以将「环形」问题转化为「两个单排」问题:
    • a. 偷第一个房屋时的最大金额 x ,此时不能偷最后一个房子,因此就是偷 [0, n - 2] 区间
      的房子;
    • b. 不偷第一个房屋时的最大金额 y ,此时可以偷最后一个房子,因此就是偷 [1, n - 1] 区
      间的房子;
    • 两种情况下的「最大值」,就是最终的结果。因此,问题就转化成求「两次单排结果的最大值」。
  • 代码示例(Java):
class Solution {
    public int rob(int[] nums) {
        int n = nums.length;
        int x = nums[0]+rob1(nums,3,n-1);
        int y = rob1(nums,2,n);
        return Math.max(x,y);
    }
    public int rob1(int[] nums,int left ,int right){
        if(left>right){
            return 0;
        }
        int n = nums.length;
        int[] f = new int[n+1];
        int[] g = new int[n+1];
        f[0] = g[0] = 0;
        for(int i = left;i<=right;i++){
            f[i] = g[i-1]+nums[i-1];
            g[i] = Math.max(f[i-1],g[i-1]);
        }
        return Math.max(f[right],g[right]);

    }
}

在这里插入图片描述

三.[leetcode] 740. 删除并获得点数

  • 题目链接: 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 个点数。

  • 解法:(动态规划)
    其实这道题依旧是「打家劫舍I」按摩师 问题的变型。我们注意到题目描述,**选择 x 数字的时候, x - 1 与 x + 1 是不能被选择的。像不像「打家劫舍」问题中,选择 i 位置的⾦额之后,就不能选择 i - 1 位置以及 i + 1 位置的金额呢~**因此,我们可以创建一个大小为 10001 (根据题⽬的数据范围)的 hash 数组,将 nums 数组中每一个元素 x ,累加到 hash 数组下标为 x 的位置处,然后在 hash 数组上来一次「打家劫舍」即可

  • 代码示例(Java):

class Solution {
    public int deleteAndEarn(int[] nums) {
        int n = 10000;
        int[] arr = new int[n+1];
        for(int i = 0;i<nums.length;i++){
            arr[nums[i]]+=nums[i];
        }
        int count = nums.length;
        //1.创建dp表
        int[] f = new int[n+1];
        int[] g = new int[n+1];
        //2.初始化
        f[0] = g[0] = 0;
        //3.填表
        for(int i = 1;i <= n; i++){
            f[i] = g[i-1] + arr[i];
            g[i] = Math.max(f[i-1],g[i-1]);
        }
        //4.返回结果
        return Math.max(f[n],g[n]);
        
    }
}

在这里插入图片描述

四.[leetcode] LCR 091. 粉刷房子

  • 题目链接: 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

  • 解法:(动态规划)

    1. 状态表示:
      根据经验+题目要求.
      但是我们这个题在 i 位置的时候,会面临「红」「蓝」「绿」三种抉择,所依赖的状态需要细分:

      • dp[i][0] 表示:粉刷到 i 位置的时候,最后一个位置粉刷上「红色」,此时的最小花费;
      • dp[i][1] 表示:粉刷到 i 位置的时候,最后一个位置粉刷上「蓝色」,此时的最小花费;
      • dp[i][2] 表示:粉刷到 i 位置的时候,最后一个位置粉刷上「绿色」,此时的最小花
        费。
    2. 状态转移方程:
      因为状态表示定义了三个,因此我们的状态转移方程也要分析三个:

      • 对于 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] 。
    3. 初始化:

      • 可以在最前面加上一个「辅助结点」,帮助我们初始化。使用这种技巧要注意两个点:
        • i. 辅助结点里面的值要「保证后续填表是正确的」;
        • ii. 「下标的映射关系」。
      • 在本题中,添加一个节点,并且初始化为 0 即可。
    4. 填表顺序:
      从左向右, 三个表一起填

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

  • 代码示例(Java):

class Solution {
    public int minCost(int[][] costs) {
        int n = costs.length;
        int[][] dp = new int[n+1][3];
        dp[0][0] = 0;
        dp[0][1] = 0;
        dp[0][2] = 0;
        for(int i = 1;i<= n; i++){
            dp[i][0] = Math.min(dp[i-1][1],dp[i-1][2])+costs[i-1][0];
            dp[i][1] = Math.min(dp[i-1][0],dp[i-1][2])+costs[i-1][1];
            dp[i][2] = Math.min(dp[i-1][0],dp[i-1][1])+costs[i-1][2];
        }
        return Math.min(Math.min(dp[n][0],dp[n][1]),dp[n][2]);
    }
}

在这里插入图片描述

五.[leetcode] 309. 买卖股票的最佳时机含冷冻期

  • 题目链接: 309. 买卖股票的最佳时机含冷冻期
  • 题目描述:
    给定一个整数数组prices,其中第 prices[i] 表示第 i 天的股票价格 。​
    设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
    卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
    注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

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

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

  • 解法:(动态规划)

    1. 状态表示:
      根据经验+题目要求.
      结合题目要求,定义一个状态表示:
      由于有「买入」「可交易」「冷冻期」三个状态,因此我们可以选择用三个数组,其中:

      • dp[i][0] 表示:第 i 天结束后,处于「买入」状态,此时的最大利润;
      • dp[i][1] 表示:第 i 天结束后,处于「可交易」状态,此时的最大利润;
      • dp[i][2] 表示:第 i 天结束后,处于「冷冻期」状态,此时的最大利润
    2. 状态转移方程:

      • 我们要谨记规则:
        • 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]应该为两种情况应的最大值,因此: 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[i][2] 只有前一天手里有股票(处于买入状态) 才能卖出,第二天才会处于冷冻期,我们只有「一种情况」能到达这个状态:
        • i. 在 i - 1 天的时候,卖出股票。
        • 因此对应的状态转移为: dp[i][2] = dp[i - 1][0] + prices[i]
    3. 初始化:

      • 可以在最前面加上一个「辅助结点」,帮助我们初始化。使用这种技巧要注意两个点:
        • i. 辅助结点里面的值要「保证后续填表是正确的」;
        • ii. 「下标的映射关系」。
      • 三种状态都会用到前一个位置的值,因此需要初始化每一行的第一个位置:
        dp[1][0] = -price[0];dp[1][1]= dp[1][2] = 0
    4. 填表顺序:
      三个表一起填, 从左向右.

    5. 返回值:
      应该返回「卖出状态」下的最大值,因此应该返回 max(dp[n][1], dp[n][2]) 。

  • 代码示例(Java):

class Solution {
    public int maxProfit(int[] prices) {
        int n = prices.length;
        if(n==1){
            return 0;
        }
        //1.创建dp表
        int[][] dp = new int[n+1][3];
        //2.初始化
        dp[0][0] = dp[0][1] = dp[0][2] = 0;
        dp[1][0] = -prices[0];
        //3.填表
        for(int i = 2;i<=n;i++){
            dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1]-prices[i-1]);
            dp[i][1] = Math.max(dp[i-1][2],dp[i-1][1]);
            dp[i][2] = dp[i-1][0] + prices[i-1];
        }
        //4.确定返回值
        return Math.max(dp[n][1],dp[n][2]);
    }
}

在这里插入图片描述

六.[leetcode] 714. 买卖股票的最佳时机含手续费

  • 题目链接: 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. 状态表示:
      根据经验+题目要求. 结合题目要求,定义一个状态表示:
      由于有「买入」「可交易」三个状态,因此我们可以选择用两个数组,其中:
      • dp[i][0] 表示:第 i 天结束后,处于「买入」状态,此时的最大利润;
      • dp[i][1] 表示:第 i 天结束后,处于「卖出」状态,此时的最大利润。
    2. 状态转移方程:
      我们选择在「卖出」的时候,⽀付这个⼿续费,那么在「买⼊」的时候,就不⽤再考虑⼿续费的问
      • 对于 dp[i][0] 买入状态可以是前一天就是买入状态,也可以是当天买入.,此时就会有「两种情况」能到达这个状态:
        • i. 在 i - 1 天「持有」股票,第 i 天啥也不干。此时最大收益为 dp[i - 1][0] ;
        • ii. 在 i - 1 天的时候「没有」股票,在第 i 天买入股票。此时最大收益为 dp[i - 1][1] - prices[i]) ;
        • dp[i][0]应该为两种情况应的最大值,因此: dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]) 。
      • 对于 dp[i][1] 可交易状态可以是前一天就是可交易状态或者前一天有股票但是今天卖了,买了之后就处于可交易状态了,此时就会有「两种情况」能到达这个状态:
        • i. 在 i - 1 天「持有」股票,但是在第 i 天将股票卖出。此时最大收益为: dp[i - 1][0] + prices[i] - fee ,记得⼿续费;
        • ii. 在 i - 1 天「没有」股票,然后第 i 天啥也不干。此时最⼤收益为: g[i - 1] ;
        • 两种情况下应该取最大值,因此 g[i] = max(g[i - 1], f[i - 1] + prices[i]
  • fee) 。
    3. 初始化:
    - 可以在最前面加上一个「辅助结点」,帮助我们初始化。使用这种技巧要注意两个点:
    - i. 辅助结点里面的值要「保证后续填表是正确的」;
    - ii. 「下标的映射关系」。
    - 三种状态都会用到前一个位置的值,因此需要初始化每一行的第一个位置:
    dp[1][0] = -price[0];dp[1][1] = 0

    1. 填表顺序:
      两个表一起填, 从左向右.
    2. 返回值:
      应该返回「卖出状态」下的最大值,因此应该返回 dp[n][1]。
  • 代码示例(Java):

class Solution {
    public int maxProfit(int[] prices, int fee) {
        int n = prices.length;
        if(n==1){
            return 0;
        }
        //1.创建dp表
        int[][] dp = new int[n+1][2];
        //2.初始化
        dp[0][0] = dp[0][1] = 0;
        dp[1][0] = -prices[0];
        //3.填表
        for(int i = 2;i<=n;i++){
            dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1]-prices[i-1]);
            dp[i][1] = Math.max(dp[i-1][1],dp[i-1][0]+prices[i-1]-fee);
        }
        //4.返回结果
        return dp[n][1];
    }
}

在这里插入图片描述

七.[leetcode] 123. 买卖股票的最佳时机 III

  • 题目链接: 123. 买卖股票的最佳时机 III
  • 题目描述:
    给定一个数组,它的第 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. 状态表示:
      根据经验+题目要求. 结合题目要求,定义一个状态表示:
      由于有「买入」「可交易」三个状态,因此我们可以选择用两个数组,但是这道题里面还有交易次数的限制,因此我们还需要再加上一维,用来表示交易次数。其中:

      • f[i][j] 表⽰:第 i 天结束后,完成了 j 次交易,处于「买入」状态,此时的最大利润;
      • g[i][j] 表示:第 i 天结束后,完成了 j 次交易,处于「卖出」状态,此时的最大利润。
    2. 状态转移方程:

      • 对于 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]);
    3. 初始化:

      • 可以在最前面加上一个「辅助结点」,帮助我们初始化。使用这种技巧要注意两个点:
        • i. 辅助结点里面的值要「保证后续填表是正确的」;
        • ii. 「下标的映射关系」。
      • 由于需要用到 i = 0 时的状态,因此我们初始化第二行即可。
        • 当处于第 0 天的时候,只能处于「买入过一次」的状态,此时的收益为 -prices[0] ,因此 f[1][0] = - prices[0] 。
        • 为了取 max 的时候,⼀些不存在的状态「起不到干扰」的作用,我们统统将它们初始化为 -INF (用 INT_MIN 在计算过程中会有「溢出」的风险,这⾥ INF 折半取0x3f3f3f3f ,足够小即可)
        • 在这里插入图片描述
    4. 填表顺序:
      从「上往下填」每一行,每一行「从左往右」,两个表「一起填」。

    5. 返回值:
      返回处于「卖出状态」的最大值,但是我们也「不知道是交易了几次」,因此返回 g 表最后一行的最大值。

  • 代码示例(Java):

class Solution {
    public int maxProfit(int[] prices) {
        int n = prices.length;
        int inf = 0x3f3f3f3f;
        //1.创建dp表
        int[][] f = new int[n+1][3];
        int[][] g = new int[n+1][3];
        //2.初始化
        for(int i =0;i<3;i++) {
            f[1][i] = g[1][i] = -inf;
        }
        f[1][0] = -prices[0] ;
        g[1][0] = 0;
        //3.填表
        for(int i = 2;i<=n;i++){
            for(int j = 0;j<3;j++){
                f[i][j] = Math.max(f[i-1][j],g[i-1][j]-prices[i-1]);
                g[i][j] = g[i-1][j];
                if(j-1>=0){
                    g[i][j] = Math.max(g[i][j],f[i-1][j-1]+prices[i-1]);
                }
            }
        }
        //4.确定返回值
        int ret=0;
        for(int i = 0 ;i<3;i++){
            ret = Math.max(ret,g[n][i]);
        }
        return ret;
    }
}

在这里插入图片描述

八.[leetcode] 188. 买卖股票的最佳时机 IV

  • 题目链接: 188. 买卖股票的最佳时机 IV
  • 题目描述:
    给你一个整数数组 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. 状态表示:
      根据经验+题目要求. 结合题目要求,定义一个状态表示:
      为了更加清晰的区分「买入」和「卖出」,我们换成「有股票」和「无股票」两个状态。

      • f[i][j] 表示:第 i 天结束后,完成了 j 笔交易,此时处于「有股票」状态的最大收益;
      • g[i][j] 表示:第 i 天结束后,完成了 j 笔交易,此时处于「⽆股票」状态的最大收益。
    2. 状态转移方程:

      • 对于 f [i][j] ,我们也有两种情况能在第 i 天结束之后,完成 j 笔交易,此时⼿⾥「有股票」的状态:
        • i. 在 i - 1 天的时候,手里「有股票」,并且交易了 j 次。在第 i 天的时候,啥也不干。此时的收益为 f[i - 1][j] ;
        • ii. 在 i - 1 天的时候,手里「没有股票」,并且交易了 j 次。在第 i 天的时候,买了股票。那么 i 天结束之后,我们就有股票了。此时的收益为 g[i - 1][j] - prices[i] ;
        • 上述两种情况,我们需要的是「最⼤值」,因此 f 的状态转移方程为:
          f[i][j] = max(f[i - 1][j], g[i - 1][j] - prices[i])
      • 对于 g [i][j] ,我们有下面两种情况能在第 i 天结束之后,完成 j 笔交易,此时⼿⾥「没有股票」的状态:
        • i. 在 i - 1 天的时候,手里「没有股票」,并且交易了 j 次。在第 i 天的时候,啥也不干。此时的收益为 g[i - 1][j] ;
        • ii. 在 i - 1 天的时候,手里「有股票」,并且交易了 j - 1 次。在第 i 天的时候,把股票卖了。那么 i 天结束之后,我们就交易了 j 次。此时的收益为 f[i - 1][j - 1] + prices[i] ;
        • 上述两种情况,我们需要的是「最⼤值」,因此 g 的状态转移⽅程为:
          g[i][j] = max(g[i - 1][j], f[i - 1][j - 1] + prices[i])
    3. 初始化:

      • 可以在最前面加上一个「辅助结点」,帮助我们初始化。使用这种技巧要注意两个点:
        • i. 辅助结点里面的值要「保证后续填表是正确的」;
        • ii. 「下标的映射关系」。
      • 由于需要用到 i = 0 时的状态,因此我们初始化第一行即可。
        • 当处于第 0 天的时候,只能处于「买入过一次」的状态,此时的收益为 -prices[0] ,因此 f[0][0] = - prices[0] 。
        • 为了取 max 的时候,⼀些不存在的状态「起不到干扰」的作用,我们统统将它们初始化为 -INF (用INT_MIN 在计算过程中会有「溢出」的风险,这⾥ INF 折半取0x3f3f3f3f ,足够小即可)
    4. 填表顺序:
      从上往下填每一行,每一行从左往右,两个表一起填。

    5. 返回值:
      返回处于卖出状态的最大值,但是我们也不知道是交易了几次,因此返回 g 表最后一行的最大值。

    6. 优化点
      我们的交易次数是不会超过整个天数的⼀半的,因此我们可以先把 k 处理⼀下,优化⼀下问题的规
      模:k = min(k, n / 2)

  • 代码示例(Java):

class Solution {
    public int maxProfit(int k, int[] prices) {
        int n = prices.length;
        int inf = 0x3f3f3f3f;
        k = Math.min(k,n/2);
        //1.创建dp表
        int[][] f = new int[n+1][k+1];
        int[][] g = new int[n+1][k+1];
        //2.初始化
        for(int i = 0;i<=k;i++){
            f[1][i] = g[1][i] = -inf;
        }
        f[1][0] = -prices[0];
        g[1][0] = 0;
        //3.填表
        for(int i = 2;i<=n;i++){
            for(int j = 0;j<= k;j++){
                f[i][j] = Math.max(f[i-1][j],g[i-1][j]-prices[i-1]);
                g[i][j] = g[i-1][j];
                if(j-1>=0){
                    g[i][j] = Math.max(g[i][j],f[i-1][j-1]+prices[i-1]);
                } 
            }
        }
        //4.确定返回值
        int ret =0;
        for(int i=0;i<=k;i++){
            ret =Math.max(ret,g[n][i]);
        }
        return ret;
   }
}

在这里插入图片描述

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

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

相关文章

职场,要想逆袭,必须要“装”

普通人&#xff0c;没有家庭背景&#xff0c;没有社会资源&#xff0c;没有好学历&#xff0c;如何才能逆袭呢&#xff1f;有朝一日自己熬出头&#xff0c;又如何避免被别人针对呢&#xff1f; 最重要的就是要会“装”。 在社会上&#xff0c;只有资源多的人才能更好的生存&a…

opencv c++ python等比缩小或放大显示图片代码

c代码&#xff0c;其中scale_percent用来设置百分比&#xff0c;例如50 就是百分之五十&#xff0c;也就是一半的大小&#xff0c;当然也可以设置成200&#xff0c;相当于原来的2倍大小&#xff0c;注意图片路径换成实际路径。 #include <opencv2/opencv.hpp>int main()…

【Java零基础视频教程】综合练习题(一)——基础练习

文章目录 基础练习飞机票打印素数生成验证码复制数组评委打分数字加密抽奖双色球 基础练习 飞机票 机票价格按照淡季旺季、头等舱和经济舱收费、输入机票原价、月份和头等舱或经济舱。 ​ 按照如下规则计算机票价格&#xff1a;旺季&#xff08;5-10月&#xff09;头等舱9折&…

pxe安装部署

RHEL7为例&#xff1a; ifconfig查看ip 一.环境配置 1.配置软件仓库&#xff1a; mkdir /rhel7 mount /dev/cdrom /rhel7 echo mount /dev/cdrom /rhel74 >> /etc/rc.d/rc,local chmod x /etc/rc.d/rc.local 2.关闭火墙和selinux&#xff0c;下载…

【极速前进】20240706-24240714:用于Agent的树搜、理解LLM的语种困惑、事实知识抽取微调、Quiet-STaR

相关博客 【极速前进】20240706-24240714&#xff1a;用于Agent的树搜、理解LLM的语种困惑、事实知识抽取微调、Quiet-STaR 【极速前进】20240615-20240623&#xff1a;Zipper融合模态、VideoLLM视频理解、WebAgent可以自我改善、Nemotron-4、AnyGPT统一模态 【极速前进】20240…

Final Shell for Mac 虚拟机连接工具【简单易操作,轻松上手】【开发所需连接工具】

Mac分享吧 文章目录 效果一、下载软件二、安装软件三、运行测试安装完成&#xff01;&#xff01;&#xff01; 效果 一、下载软件 下载软件 链接&#xff1a;http://www.macfxb.cn 二、安装软件 三、运行测试 安装完成&#xff01;&#xff01;&#xff01;

Kubernets(k8s) 网络原理三:同主机内Pod相互访问

前两篇文章中我们介绍了pod怎么和宿主机通信以及pod怎么访问外网&#xff0c;这两种通信是理解pod间通信的基础。 关于pod间的相互访问&#xff0c;这里还需要细化一下。回想一下pod在k8s节点中的分布&#xff0c;两个pod可能分布在同一台宿主机上&#xff0c;也可能分布在不同…

可视化图表与页面源代码显示

可视化图表与页面源代码显示 页面效果&#xff1a; <!DOCTYPE html> <html lang"en" style"height: 100%"> <head><meta charset"utf-8"><title>饼状图</title><style>body {display: flex;height:…

基于51单片机的交通信号灯proteus仿真设计

1.功能简介 交通信号灯是一种经典应用电路&#xff0c;本设计基于51单片机&#xff0c;利用Proteus仿真软件构建了一个模拟交通信号灯系统。该系统能够模拟真实交通环境中的信号变化&#xff0c;包括红灯、黄灯和绿灯的切换&#xff0c;以及倒计时显示等功能&#xff0c;各种灯…

谷歌出品,一款免费的智能绘图工具

AutoDraw是由Google开发的一款基于网络的智能绘图工具&#xff0c;旨在通过人工智能技术帮助用户快速、简便地创建图画和图表。该工具于2017年4月11日由谷歌创意实验室推出&#xff0c;并迅速获得了广泛关注。 AutoDraw的核心功能是利用机器学习算法识别用户的草图或涂鸦&…

C++STL专题-string类

目录 1.标准库中的string类 1.1 string类 2.2 auto和范围for 2.2.1 auto关键字 2.2.2 范围for 2.3 string类的常用接口讲解 1.string类对象的常见构造 2.元素访问 3.迭代器(iterator) 3.1 begin 和 end 3.2 rbegin 和 rend 4.容器 5.修改 5.1 append 5.2 assign…

在C#中为图片添加数字水印的几种办法

最近在写个人项目时&#xff0c;有遇到需要将图片加上水印防止被盗取的需求。这里找了几种实现方式&#xff0c;可供有需要的朋友参考。 本身我不是搞算法这块的&#xff0c;所以这里只是找了一些实现&#xff0c;也没有继续深究下去。 以前在学校的时候从书上了解过可以将一…

cmake常用命令学习

1.include https://blog.csdn.net/qq_38410730/article/details/102677143 CmakeLists.txt才是cmake的正统文件&#xff0c;而.cmake文件是一个模块文件&#xff0c;可以被include到CMakeLists.txt中。 include指令一般用于语句的复用&#xff0c;也就是说&#xff0c;如果有…

OBS Studio:如何打造专业级的视频直播体验

1.简介 OBS&#xff08;Open Broadcaster Software&#xff09;是一款开源的视频录制和直播软件&#xff0c;广泛用于视频制作、游戏直播和网络直播。它支持多种操作系统&#xff0c;包括Windows、macOS和Linux。OBS提供了丰富的功能&#xff0c;包括但不限于&#xff1a; **…

聊聊跨境电商平台与固定IP的那些事

IP地址网络地址&#xff08;网络号&#xff09;主机地址&#xff08;地址号&#xff09;&#xff0c;IP地址是一台电脑在网络中的唯一标识&#xff0c;可分为固定IP与动态IP。那么IP地址的分类有哪些&#xff1f;什么IP适合亚马逊/eBay/速卖通等平台运营时使用&#xff1f; A类…

Spring5 的日志学习

我们在使用 Spring5 的过程中会出现这样的现像&#xff0c;就是 Spring5 内部代码打印的日志和我们自己的业务代码打印日志使用的不是统一日志实现&#xff0c;尤其是在项目启动的时候&#xff0c;Spring5 的内部日志使用的是 log4j2&#xff0c;但是业务代码打印使用的可能是 …

DNS安全概述

一、DNS的解析过程 1.递归解析 递归解析是一种由DNS客户端&#xff08;通常是用户的应用程序&#xff0c;如一个浏览器&#xff09;向本地DNS解析器发出解析请求&#xff0c;然后本地DNS解析器负责查询最终结果并将结果返回给客户端&#xff0c;而中间的所有查询请求都由本地D…

每日OJ_牛客HJ62 查找输入整数二进制中1的个数

目录 牛客HJ62 查找输入整数二进制中1的个数 解析代码 牛客HJ62 查找输入整数二进制中1的个数 查找输入整数二进制中1的个数_牛客题霸_牛客网 解析代码 本题是计算一个数二进制表示中1的个数&#xff0c;通过&#xff08;n >> i) & 1可以获取第i位的二进制值&…

基于Spring boot + Vue的校园论坛

作者的B站地址&#xff1a;程序员云翼的个人空间-程序员云翼个人主页-哔哩哔哩视频 csdn地址&#xff1a;程序员云翼-CSDN博客 1.项目技术栈&#xff1a; 前后端分离的项目 后端&#xff1a;Springboot MybatisPlus 前端&#xff1a;Vue ElementUI 数据库&#xff1a; …

聚星文社 聚星3高级推理创作

聚星文社 聚星3高级推理创作 Docshttps://qvfbz6lhqnd.feishu.cn/wiki/D3YLwmIzmivZ7BkDij6coVcbn7W