【动态规划】简单多状态 dp 问题

news2024/11/22 21:14:38

简单多状态 dp 问题

  • 1.面试题 17.16. 按摩师
  • 2.打家劫舍 II
  • 3.删除并获得点数
  • 4.粉刷房子
  • 4.买卖股票的最佳时机含冷冻期
  • 5.买卖股票的最佳时机含手续费
  • 6.买卖股票的最佳时机 III
  • 7.买卖股票的最佳时机 IV

在这里插入图片描述

点赞👍👍收藏🌟🌟关注💖💖
你的支持是对我最大的鼓励,我们一起努力吧!😃😃

1.面试题 17.16. 按摩师

题目链接: 面试题 17.16. 按摩师

题目分析:

在这里插入图片描述

圆圈表示预约,每个预约可以接或不接,不能接受相邻的预约,最优的预约集合(总预约时间最长),返回总的分钟数。
在这里插入图片描述

算法原理:

1.状态表示

经验 + 题目要求

dp[i] 表示:选择到 i 位置的时候,此时最长预约时长。

但是 i 位置可以选或者不选,因此继续细化

f[i] 表示:选择到 i 位置的时候,nums[i] 必选,此时最长预约时长

g[i] 表示:选择到 i 位置的时候,nums[i] 不选,此时最长预约时长

在这里插入图片描述

2.状态转移方程

f[i] 表示 必选 i 位置,此时最长预约时长。i 位置可以选,那 i - 1 位置就不能选了,而我们想要的是最长预约时长,那就把 0 ~ i- 1 区间内最长预约时长给我,这个最长预约时长保证 i - 1 位置必不选。而 g[i-1] 表示 i -1位置不选此时最长预约时长。当然也别忘记 i 位置的预约时长

在这里插入图片描述
g[i] 表示 不选 i 位置,此时最长预约时长。 i 位置不选,那 i - 1 位置可选可不选,我们要找的是 0 ~ i - 1区间最长预约时长,有两种情况,选 i - 1 位置 ,而 f[i - 1] 表示必选 i - 1 位置此时最长预约时长, 不选 i - 1 位置,g[i - 1] 表示 不选 i - 1 位置此时最长预约时长

在这里插入图片描述

3.初始化

填表时不越界

这里我们也可以添加虚拟节点,但是这道题初始化太简单了,因此就不要添加虚拟节点。填f[0],g[0] 会越界,而f[0]表示必选0位置,g[0]表示不选0位置,因此

f[0] = nums[0] ,g[0] = 0

4.填表

从左往右两个表一起填

5.返回值

返回到达最后一个位置时最长预约时长

max( f[n - 1],g[n - 1] )

class Solution {
public:
    int massage(vector<int>& nums) {
        // 1.创建dp表
        // 2.初始化
        // 3.填表
        // 4.返回值

        int n = nums.size();
        if(n == 0) return 0;//处理边界情况
        vector<int> fdp(n);
        vector<int> gdp(n);
        fdp[0] = nums[0], gdp[0] = 0;
        for(int i = 1; i < n; ++i)
        {
            fdp[i] = gdp[i - 1] + nums[i];
            gdp[i] = max(fdp[i - 1],gdp[i - 1]);
        }

        return max(fdp[n - 1], gdp[n - 1]);
    }
};

2.打家劫舍 II

题目链接: 213. 打家劫舍 II

题目分析:

在这里插入图片描述

这道题相比于打家劫舍I 多了这个条件:这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的,也就是说现在成了一个环路了。

同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。

算法原理:

分类讨论:

根据第一个位置选or不选可以把问题从环形问题转化为线性问题

在这里插入图片描述
接下来是打家劫舍I的分析,和上面那道题几乎一模一样

1.状态表示

经验 + 题目要求

f[i] 表示:偷到 i 位置的时候,偷 nums[i] ,此时的最大金额

g[i] 表示:偷到 i 位置的时候,不偷 nums[i] ,此时的最大金额

2.状态转移方程

在这里插入图片描述

3.初始化

填表时不越界

f[0] = nums[0] ,g[0] = 0

4.填表

从左往右两个表一起填

5.返回值

返回偷到最后一个位置时最大金额

max( f[n - 1],g[n - 1] )

class Solution {
public:
    int rob(vector<int>& nums) {
        int n = nums.size();

        //分类讨论两种情况下最大值
        return max(dp(nums, 2, n-2) + nums[0], dp(nums, 1, n - 1));
    }

    int dp(vector<int>& nums, int left, int right)
    {
        if(left > right) return 0;
        // 1.创建 dp 表
        // 2.初始化
        // 3.填表
        // 4.返回值

        int n = nums.size();
        vector<int> f(n);
        vector<int> g(n);
        f[left] = nums[left], g[left] = 0;

        for(int i = left + 1; i <= right; ++i)
        {
            f[i] = g[i - 1] + nums[i];
            g[i] = max(f[i - 1], g[i - 1]);
        }

        return max(f[right], g[right]);
    }
};

3.删除并获得点数

题目链接: 740. 删除并获得点数

题目分析:

在这里插入图片描述

给你一个整数数组 nums ,你可以对它进行一些操作。

每次操作中,选择任意一个 nums[i] ,删除它并获得 nums[i] 的点数。之后,你必须删除 所有 等于 nums[i] - 1 和 nums[i] + 1 的元素。

开始你拥有 0 个点数。返回你能通过这些操作获得的最大点数。

对于删除它并获得 nums[i] 的点数。之后,你必须删除 所有 等于 nums[i] - 1 和 nums[i] + 1 的元素。删除 所有 等于 nums[i] - 1 和 nums[i] + 1 的元素也就是说值等于这两个的数不能选了。因此我们可以先对数组排一下序,方便找到 nums[i] - 1 和 nums[i] + 1 的 元素。

在这里插入图片描述

算法原理:

一定要有这样的能力,碰到一个新题的时候,可以往之前做过的题方向靠!

比如这道题如果是有序的并且中间数没有少,就像打家劫舍的问题。比如说1、2、3、4、5。 选 1 之后不能选 2 了,是不是就是不能选择相邻的两个数并且要最终选择的数最大。

在这里插入图片描述

所以如果说,数组是有序的话并且中间没有间隔,就是 “打家劫舍” 问题。

但是这里的数并不是那么连续的。如果中间数断了,就不是 “打家劫舍” 问题。
比如这里选 2 之后,还能选 4.

在这里插入图片描述

所以我们可以先用数组arr,把这些数先统计一下。下标 i 表示这个数是几,arr[i] 表示 " i " 这个数出现的总和。为什么预处理到数组中,因为 下标 i 是有序的。

在这里插入图片描述

然后就可以不要nums了,相当于在arr数组做一次 “打家劫舍” 。比如说选了下标1里面的数,那下标 2 里面的数就不能要了,也就相当于选择当前这个数,相邻的数不能选了。可以选后面其他的数。

在这里插入图片描述

预处理:将数组中的数,统计到 arr 中,然后在 arr 中,做一次 “打家劫舍” 问题即可

1.状态表示

经验 + 题目要求

f[i] 表示:选到 i 位置的时候, nums[i] 必选 ,此时能获得的最大点数

g[i] 表示:偷到 i 位置的时候,nums[i] 不选,此时能获得的最大点数

2.状态转移方程

在这里插入图片描述

3.初始化

填表时不越界

f[0] = arr[0] ,g[0] = 0

4.填表

从左往右两个表一起填

5.返回值

返回偷到最后一个位置时最大金额

max( f[n - 1],g[n - 1] )

class Solution {
public:
    int deleteAndEarn(vector<int>& nums) {
        // 1. 预处理
        const int n = 10001;
        int arr[n] = {0};
        for(auto e : nums)
        {
            arr[e] += e;
        }

        // 2.在 arr 数组上,做一次 "打家劫舍" 问题

        // 1. 创建 dp 表
        // 2. 初始化
        // 3. 填表
        // 4. 返回值
        vector<int> f(n);
        vector<int> g(n);
        //这道题可以不用初始化
        for(int i = 1; i < n ; ++i)
        {
            f[i] = g[i - 1] + arr[i];
            g[i] = max(f[i - 1], g[i - 1]);
        }
        return max(f[n - 1], g[n - 1]);
    }
};

4.粉刷房子

题目链接: LCR 091. 粉刷房子

题目分析:

在这里插入图片描述

有n个房子,每个房子都可以被粉刷成红色,蓝色,绿色,相邻的房子颜色不能相同。每个房子粉刷成不同颜色的价格由一个3*n的矩阵给出,左边0 ~ 2表示房子,上面0 ~ 2表示每个房子被粉刷不能颜色的价格。要找到粉刷完所有房子最少的价格。

在这里插入图片描述

算法原理:

1.状态表示

经验 + 题目要求

以 i 位置为结尾,当涂到 i 位置的时候此时的最小花费,但是涂到 i 位置还可以细分,涂成红色,蓝色,绿色。

dp[i][0] 表示:粉刷到 i 位置的时候,最后一个位置粉刷上 红色,此时的最小花费

dp[i][1] 表示:粉刷到 i 位置的时候,最后一个位置粉刷上 蓝色,此时的最小花费

dp[i][2] 表示:粉刷到 i 位置的时候,最后一个位置粉刷上 绿色,此时的最小花费

在这里插入图片描述

2.状态转移方程

此时分三种情况讨论:

如果 i 位置涂成红色这个位置花费已经固定了 cost[i][0],我要的是最小花费,只要保证 0 ~ i -1 区间花费最小就行了。涂到 i - 1 位置只能涂成蓝色、绿色,dp[i-1][1]表示涂到 i - 1 位置涂蓝色此时最小花费,dp[i-1][2]表示涂到 i - 1 位置涂绿色此时最小花费,因此 i 位置涂成红色状态转移方程就处理了,剩下也是这样的处理:

在这里插入图片描述

3.初始化

填表时不越界

这里我们可以多申请一个空间。这样就可以把初始化放到填表里面了。但是要注意两点:

  1. 虚拟节点里面的值,要保证后序填表时正确的
  2. 下标的映射关系

首先看虚拟节点里面的值应该填多少,可以先考虑没有虚拟节点第一个位置应该填多少,是不是填的是自己本身啊,因此虚拟节点里面的值我们给0。

因为我们多开一个空间,相当于整体向右移一位,如果要回去应该 下标应该减 1。

在这里插入图片描述

4.填表

从左往右三个表一起填

5.返回值

返回涂到最后一个位置,涂成红,蓝,绿最小花费

min(dp[n][0],dp[n][1],dp[n][2])

class Solution {
public:
    int minCost(vector<vector<int>>& costs) {
        // 1. 创建 dp 表
        // 2. 初始化
        // 3. 填表
        // 4. 返回值

        int n = costs.size();
        vector<vector<int>> dp(n + 1, vector<int>(3));

        for(int i = 1; i <= n; ++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];
        }

        return min(dp[n][0], min(dp[n][1], dp[n][2]));
    }
};

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

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

题目分析:

在这里插入图片描述

卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。

这里的「处于冷冻期」指的是在第 i 天结束之后的状态。也就是说:如果第 i 天结束之后处于冷冻期,那么第 i+1 天无法买入股票。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)
在这里插入图片描述

算法原理:

1.状态表示

经验 + 题目要求

dp[i] 表示 第 i 天结束之后,此时最大利润。继续细分:

i 天结束之后,此时处于 " 买入" 状态

i 天结束之后,此时处于 " 可交易(卖出后手里没有股票了并且此时不处于冷冻期)" 状态

i 天结束之后,此时处于 " 冷冻期 " 状态

dp[i][0] 表示: 第 i 天结束之后,处于 " 买入 " 状态,此时的最大利润
dp[i][1] 表示: 第 i 天结束之后,处于 " 可交易 " 状态,此时的最大利润
dp[i][2] 表示: 第 i 天结束之后,处于 " 冷冻期" 状态,此时的最大利润

在这里插入图片描述

2.状态转移方程

此时分三种情况讨论:

可以画一个状态机,看自己能不能到自己,自己能不能到其他。

在这里插入图片描述
比如在 i 天结束处于买入状态,看 i - 1 天是什么状态然后满足 i 天结束处于买入状态。

如果 i - 1 天结束处于买入状态,那 i 天啥也不干,依旧处于 买入状态。
如果 i - 1 天结束处于冷冻期,也就说 i 天不能进行任何操作,所以进不了 买入状态。
如果 i - 1 天结束处于可交易状态,只要把 i 天 股票买了就进入买入状态。

在这里插入图片描述
在 i 天结束处于冷冻期状态,看 i - 1 天是什么状态然后满足 i 天结束处于冷冻期

i - 1 天结束处于冷冻期不能 i 天也处于冷冻期,不能卖了还卖只有一支股票。
i - 1 天结束处于买入状态,i 天卖掉股票,i天之后就处于冷冻期了。
i - 1 天结束处于可交易,手里没有股票可以卖,不能到冷冻期

在这里插入图片描述
在 i 天结束处于可交易状态,看 i - 1 天是什么状态然后满足 i 天结束处于可交易

i - 1 天结束处于可交易,i 天啥也不干,还是可交易状态。
i - 1 天结束处于买入状态,i天先要把股票卖了i天结束之后处于冷冻期,才能进入可交易。
i - 1 天结束处于冷冻期,i 天啥也不干,就进入i天结束处于可交易

在这里插入图片描述

在这里插入图片描述

3.初始化

填表时不越界

dp[0][0] (买入状态) = -p[0]
dp[0][1] (可交易) = 0
dp[0][2] (冷冻状态) = 0

4.填表

从左往右三个表一起填

5.返回值

返回最后一个位置最大利润,i 天 还买股票肯定是不行的。

max(dp[n-1][1],dp[n-1][2])

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        // 1.创建 dp 表
        // 2.初始化
        // 3.填表
        // 4.返回值

        int n = prices.size();
        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][0],dp[i-1][1] - prices[i]);
            dp[i][1] = max(dp[i-1][1],dp[i-1][2]);
            dp[i][2] = dp[i-1][0] + prices[i];
        }
        return max(dp[n-1][1],dp[n-1][2]);

    }
};

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

题目链接: 714. 买卖股票的最佳时机含手续费v

题目分析:

在这里插入图片描述

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

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

返回获得利润的最大值。

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

上面意思是,买卖一次股票,要支付手续费,要不就是买支付要么就是卖支付,只用支付一次。并且手里只能有一支股票,卖出去之前不能再次买入。你可以无限次完成交易,返回获得利润的最大值。

在这里插入图片描述

算法原理:

1.状态表示

经验 + 题目要求

dp[i] 表示 第 i 天结束之后,此时最大利润。但第 i 天结束之后可以细化为两种状态:

f[i] 表示:第 i 天结束之后,处于 “买入” 状态,此时的最大利润
g[i] 表示:第 i 天结束之后,处于 “卖入” 状态,此时的最大利润

我们可以搞两个dp表或者开一个dp(2*n)表
在这里插入图片描述

2.状态转移方程

如果是多状态,并且多状态之间可以相互转移,为了可以不重不漏的把所有情况都考虑到,建议画一个状态机,先看自己能不能到自己,在看其他的能不能到自己。 这样就可以把所有情况不重不漏的找出来。然后状态转移方程就可以根据这个图直接得到。

在这里插入图片描述
i 天结束之后处于买入状态,就看 i - 1 天结束的状态。

i - 1 天结束处于买入状态,i 天啥也不干,也处于买入状态
i - 1 天结束处于卖出状态,i 天把股票买了,i 天结束处于买入状态

在这里插入图片描述

i 天结束之后处于卖出状态,就看 i - 1 天结束的状态。

i - 1 天结束处于卖出状态,i 天啥也不干,i 天结束之后也处于卖出状态
i - 1 天结束处于买入状态,i 天把股票卖掉,i 天结束之后处于卖出状态

别忘记买卖股票是有手续费的,这里我们在卖出股票交手续费。

在这里插入图片描述

状态转移方程就出来了
在这里插入图片描述

3.初始化

可以申请虚拟节点,但是没必要。
这里填f[0] 和 g[0] 会越界。我们处理一下。

f[0] 表示第 0 天结束之后,处于 “买入” 状态,那就把股票买了就行了,
f[0] = -p[0]

g[0] 表示第 0 天结束之后,处于 “卖出” 状态,卖出手里得有股票啊但是并没有,那第 0 天啥也不干不,不还是处于卖出状态吗
g[0] = 0

4.填表

从左往右,两个表一起填

5.返回值

如果最后一天处于买入状态,你能获得最大利润吗?肯定不如最后一天卖入状态!

返回 g[n-1]

class Solution {
public:
    int maxProfit(vector<int>& prices, int fee) {
        // 1. 创建 dp 表
        // 2. 初始化
        // 3. 填表
        // 4. 返回值

        int n = prices.size();
        vector<int> f(n);
        vector<int> g(n);
        f[0] = -prices[0];
        for(int i = 1; i < n; ++i)
        {
            f[i] = max(f[i - 1], g[i - 1] - prices[i]);
            g[i] = max(g[i - 1], f[i - 1] + prices[i] - fee);
        }
        return g[n-1];
    }
};

6.买卖股票的最佳时机 III

题目链接: 123. 买卖股票的最佳时机 III

题目分析:

在这里插入图片描述

这道题还是买卖股票求最大利润,但是和前面无限次交易相比,你最多可以完成 两笔 交易。也就是说只能买卖两次!并且手里股票卖了之后才能在买股票。

在这里插入图片描述

算法原理:

1.状态表示

经验 + 题目要求

dp[i] 表示 第 i 天结束之后,所获得得最大利润,但是股票问题在第 i 天结束之后可以划分很多子状态,比如第 i 天结束手里有股票或者没有股票,此时就可以分为"卖入" 和 “卖出” 状态,而且这道题更为特殊最多只能完成两笔交易,所以我们可以有更多子状态:

第 i 天结束 ,完成 0 笔交易 此时处于 买入 状态、完成 1 笔交易 此时处于 买入 状态、完成 2 笔交易 此时处于 买入 状态。

第 i 结束,完成 0 笔交易 此时处于 卖出 状态,完成 1 笔交易 此时处于 卖出 状态,完成 2 笔交易 此时处于 卖出 状态。

所以不仅要有买入、卖出状态还要加上交易次数。因此我们定义两个二维数组,
i 表示天数,j表示交易次数。
在这里插入图片描述

f[i][j] 表示: 第 i 天结束之后,完成了 j 次交易,此时处于 “买入” 状态下的,最大利润

g[i][j] 表示: 第 i 天结束之后,完成了 j 次交易,此时处于 “卖入” 状态下的,最大利润

2.状态转移方程

如果有多个状态并且多个状态之间还能相互转化,建议画个状态机。

这条线的起点表示 i - 1 天结束之后的状态,线表示 i 天的操作,箭头所指的是当天结束之后的状态。

在这里插入图片描述
i 天结束之后处于买入状态:

i - 1 天结束之后也处于买入状态,i 天啥也不干, i 天结束之后还是处于买入状态
i - 1 天结束之后处于卖出状态,i 天买入当天股票,i 天结束之后处于买入状态

在这里插入图片描述

i 天结束之后处于卖出状态:

i - 1 天结束之后处于卖出状态,i 天啥也不干,i 天结束之后还是处于卖出状态
i -1 天结束之后处于买入状态,i 天卖出股票,i 天结束之后处于卖出状态

但是这道题要注意,这道题要把交易次数加上,在转化的过程那一步会改变交易的次数呢?
前一天买了股票,如果今天把股票给卖了之后,在今天结束之后,是不是相当于完成了一笔交易,然后交易次数+1。

在这里插入图片描述

此时状态转移方程就出来了:

f[i][j] = max(f[i-1][j](前一天处于买入状态,今天啥也不干,交易次数不变),g[i-1][j](前一天处于卖出状态, 今天只是买股票,并没有卖,没有完成交易,因此交易次数不变)- p[i])

在这里插入图片描述

g[i][j] = max(g[i-1][j](前一天完成了一笔交易,但是今天啥也不干,交易次数不变),f[i-1][j-1](第 i 天结束之后 j 的交易次数要 + 1,我要找到昨天的交易次数那是不是 j 要 -1。也就是说今天是交易2次的话,我要找到昨天交易1次的次数买入,今天卖掉交易次数不就是变成2次了, 所以是 j - 1)+ p[i])

在这里插入图片描述

3.初始化

f 要用到 f的 i-1,g的 i-1,因此要初始化 f 第一行 和 g 第一行。
g要用到 g的i-1,f的 i-1,j-1。
总结来说 f 要初始化第一行第一列,g 初始化第一行。
碰到这种情况其实有一种技巧,通过修改状态转移方程
g[i][j],当 j = 0的时候,f[i-1][j-1] j - 1会越界,表示在 i - 1 天完成了 -1 笔交易,这种状态根本不存在,所以可以修改状态转移方程,满足的情况的时候在比较。

在这里插入图片描述

为什么这样初始化,分析f表发现初始f和g表第一行就可以,分析g表发现某个位置会影响初始化,所以就把这个位置单独处理。所以初始化只用初始化第一行就可以了。如何处理,加一个判断即可!

目前我们已经学了三个初始化技巧!

不过这里还没有完,f表和g表第一行初始看给什么。

因为我们交易次数是有限制的,所以要珍惜交易次数,尽量不要当天买了之后又卖。所以第 0 天的时候,完成一笔交易、两笔交易是不存在的,并且因为我们求得是max不想它们干扰后面的结果,所以我们可以给第 0 天给买和卖 第一笔交易、两笔交易都给-∞。

在这里插入图片描述

f[0][0]表示: 第 0 天结束之后,完成了 0 次交易,此时处于 “买入” 状态下的,是不是把第 0 天股票买了才行 ,所以 f[0][0] = -p[0]

g[0][0] 表示: 第 0 天结束之后,完成了 0 次交易,此时处于 “卖入” 状态下的,卖出是手里没有股票的状态,那第 0 天我什么都不敢不就好了,所以g[0][0] = 0

但是这里有个细节

如果你选的是 INI_MIN 去初始化,这里发生减操作,INI_MIN - 1 绝对会越界!!!超出int的范围。因此:

如果是求最大值 不要选 INI_MIN,选择 - 0x3f3f3f3f 去初始化,(0x3f3f3f3f是是int最大值的一半)。这个数的好处在于足够小。并且在这个数上做加减操作不会越界!

求最小值,不要选 INI_MAX,选择 -0x3f3f3f3f 去初始化。

在这里插入图片描述

4.填表顺序

从上到下填写每一行,每一行从左往右,两个表一起填

5.返回值

最大利润可能是交易0次、1次、2次,所以返回g表的最后一行里面的最大值。
f 表第 i 填处于买入状态,根本不可能比 g 表第 i 填处于卖出状态利润大。

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        // 1.创建 dp 表
        // 2.初始化
        // 3.填表
        // 4.返回值
        const int INF = 0x3f3f3f3f;
        int n = prices.size();
        vector<vector<int>> f(n, vector<int>(3,-INF));
        auto g = f;
        f[0][0] = -prices[0];
        g[0][0] = 0;
        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]);
                g[i][j] = g[i-1][j];
                if(j - 1 >= 0) //如果该状态存在
                    g[i][j] = max(g[i][j], f[i-1][j-1] + prices[i]);
            }
        }

        //找到最后一行最大值
        int ret = 0;
        for(auto& e : g[n-1])
        {
            ret = max(e,ret);
        }
        return ret;
    }
};

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

题目链接: 188. 买卖股票的最佳时机 IV

题目分析:

在这里插入图片描述

这道题和上面的题几乎完全一模一样,上面最多只能进行两笔交易,而这道题最多能完成 k 笔交易。也就是小于等于k笔交易都行

在这里插入图片描述

算法原理:

和上面几乎一模一样,不过可以这里可以处理一个细节问题,可以提高时间复杂度和空间复杂度

如果给了 20 天 , k = 30,可以进行30笔交易。但是 20 天最多只能进行 20 / 2 =10笔交易,k 给多了就是浪费,因此处理一下k, k = min(k,n / 2)

class Solution {
public:
    int maxProfit(int k, vector<int>& prices) {

        //处理细节问题
        int n = prices.size();
        k = min(k, n / 2);

        // 1.创建 dp 表
        // 2.初始化
        // 3.填表
        // 4.返回值

        const int INF = 0x3f3f3f3f;
        vector<vector<int>> f(n, vector<int>(k + 1,-INF));
        auto g = f;
        f[0][0] = -prices[0], g[0][0] = 0;
        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]);
                g[i][j] = g[i - 1][j];
                if(j >= 1)//如果状态存在
                    g[i][j] = max(g[i][j], f[i - 1][j - 1] + prices[i]);
            }
        }
        int ret = 0;
        for(auto& e : g[n - 1])
            ret = max(ret, e);
        return ret;
    }
};

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

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

相关文章

前端技术(四)—— 最经典Node.JS全套教程

一、node简介 1. 浏览器中的 JavaScript 的组成部分 2. 思考&#xff1a;为什么 JavaScript 可以在浏览器中被执行 3.思考&#xff1a;为什么 JavaScript 可以操作 DOM 和 BOM 4. 浏览器中的 JavaScript 运行环境 5. 思考&#xff1a;JavaScript 能否做后端开发 6. Node.js介绍…

MySQL 的半同步模式

目录 1 半同步简介: 解决主从数据一致性问题 2 实现半同步模式实践操作 2.1 MASTER 2.2 SLAVE 1 2.3 SLAVE 2 2.4 查看client链接状态 2.5 SLAVE 服务器故障模拟 2.5.1 停止 SLAVE 的 IO_THREAD 2.5.2 查看SLAVE 的IO线程是否关闭 2.5.3 查看 MASTER 上 client 的连接状态…

专业视频编辑和制作软件Adobe Media Encoder(ME)win/mac下载安装和软件介绍

一、软件概述 1.1 软件简介 Adobe Media Encoder&#xff08;ME&#xff09;是由Adobe公司开发的一款专业视频编辑和制作软件&#xff0c;全称为Media Encoder&#xff0c;是Creative Cloud套件中的一个重要组件。Adobe ME以其强大的视频编码、转码、调整、剪辑、合成等功能&…

解决git checkout -b 拉取远端某分支到本地时报错

问题描述 日常开发场景中&#xff0c;经常会出现切分支的情况&#xff0c;所以git checkout 命令是非常高频的 git checkout -b feature/xxx默认情况下&#xff0c;这条命令是基于当前所在分支来开辟新分支feature/xxx 但是&#xff0c;还有一些情况&#xff0c;我们需要基于…

比Maven快2~10倍的编译工具mvnd简介与实战

概述 maven-mvnd&#xff0c;可简称&#xff08;或缩写&#xff09;mvnd&#xff0c;the Maven Daemon。Apache Maven团队借鉴Gradle和Takari后开发的更快的构建工具。mvnd内嵌Maven&#xff0c;开发者可无缝从Maven迁移到mvnd。 参考资料&#xff1a;GitHub。 mvnd中会启动…

C++ | Leetcode C++题解之第368题最大整除子集

题目&#xff1a; 题解&#xff1a; class Solution { public:vector<int> largestDivisibleSubset(vector<int>& nums) {int len nums.size();sort(nums.begin(), nums.end());// 第 1 步&#xff1a;动态规划找出最大子集的个数、最大子集中的最大整数vect…

『基础』线性代数-1行列式

行列式是什么-运算规则 排列&#xff1a;不同的 n 元排列共有 n! 个 逆序&#xff1a;小数排在大数后面&#xff0c;叫逆序&#xff1b;一个排列中逆序的总和叫做这个排列的逆序数&#xff0c;记为 τ ( j 1 , . . . , j n ) \tau(j_1,...,j_n) τ(j1​,...,jn​) 逆序数的计…

MySQL笔记01: MySQL入门_1.2 MySQL下载安装与配置

2.2 MySQL下载安装与配置 2.2.1 MySQL下载 MySQL中文官网&#xff1a;https://www.mysql.com/cn/ MySQL英文官网&#xff1a;https://www.mysql.com/ MySQL官网下载地址&#xff1a;https://www.mysql.com/downloads/ &#xff08;1&#xff09;点击“MySQL Community (GPL) Do…

WPF—画刷(使用画刷实现背景颜色渐变效果)

WPF—画刷(使用画刷实现背景颜色渐变效果) 在WPF中我们可以使用画刷来对我们的页面做出各种炫丽的效果&#xff0c;列如渐变&#xff0c;渲染等&#xff0c; 下列就为大家分享2个渐变效果示例&#xff1a; 实例1(由内到外) <Grid><!--背景颜色渐变 画刷--><G…

【项目】微服务及时通讯系统:编写核心类

文章目录 前言1. 核心数据结构1.1 用户信息1.2 会话信息1.3 消息信息 2. 建立目录3. 编写代码3.1 用户信息3.2 会话信息3.3 消息信息3.4 工具函数 4. data.h 完整代码总结 前言 在构建现代微服务架构的即时通讯系统时&#xff0c;核心数据结构的设计是至关重要的。它们不仅决定…

聊一聊AI能够应用在哪些地方

AI大模型的应用场景极为广泛&#xff0c;涵盖了众多行业和领域&#xff0c;以下是一些主要的应用场景&#xff1a; 1. 自然语言处理&#xff08;NLP&#xff09; 对话系统&#xff1a;如智能客服、虚拟助手等&#xff0c;通过自然语言与用户进行交互&#xff0c;提供信息查询…

探索AI大模型量化前沿技术:引领智能计算新潮流

大型语言模型&#xff08;LLMs&#xff09;通常因为体积过大而无法在消费级硬件上运行。这些模型可能包含数十亿个参数&#xff0c;通常需要配备大量显存的GPU来加速推理过程。 因此越来越多的研究致力于通过改进训练、使用适配器等方法来缩小这些模型的体积。在这一领域中&am…

【CAN总线测试】——通讯相关诊断测试

从0开始学习CANoe使用 从0开始学习车载测试 相信时间的力量 星光不负赶路者&#xff0c;时光不负有心人。 目录 1.节点超时故障 2.Busoff故障码测试 3.Busoff 状态下超时故障监测测试 4.欠压故障测试 5.过压故障测试 1.节点超时故障 用例编号 TG4_TC1 测试目的 检测D…

从零开始搭建 LVS 高可用集群 (单机)

从零开始搭建 LVS 高可用集群 (单机) 背景 从零开始搭建 LVS 高性能集群 (DR模式) 从零开始搭建 KeepalaivedLvs 高可用集群 &#xff08;Aliyun部署&#xff09; 经过前面2篇关于lvs集群部署文章&#xff0c;相信跟着部署文档&#xff0c;实际部署过集群的大家对lvs服务有了…

【html+css 绚丽Loading】000015 九转轮回珠

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享htmlcss 绚丽Loading&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495…

Total Uninstall - 专业 Windows 卸载清理工具,让软件卸载更彻底

Total Uninstall 是一款专业的卸载清理工具&#xff0c;可帮助我们无残留卸载各类软件。 这款工具相当强大&#xff0c;卸载能做到彻底清除软件痕迹&#xff0c;同时具备软件分析、安装记录、系统清理、软件备份搬家等多种功能。 软件支持买前免费试用&#xff0c;感兴趣的朋友…

新疆旅游今年为什么这么火热?

今年新疆旅游火爆全网&#xff0c;不夸张的说&#xff0c;打开朋友圈&#xff0c;几乎一半人在新疆旅游、还有一半人在去新疆旅游的路上。 大家也纷纷在小红书上晒出新疆相关的笔记&#xff0c;覆盖旅游、美食、穿搭、养生、摄影等众多热门行业&#xff0c;相关话题多次登上小…

MySQL从入门到精通(第5-8章)

文章目录 5 排序与分页5.1 排序数据5.1.1 排序规则5.1.2 单列排序5.1.3 多列排序 5.2 分页5.2.1 实现规则5.2.2 拓展 6 多表查询6.1 笛卡尔积6.1.1 笛卡尔积&#xff08;或交叉连接&#xff09;的理解6.1.2 笛卡尔积可能存在的问题与解决 6.2 多表查询分类讲解6.2.1 等值连接与…

AI如何让销售更智能更精确?AI赋能销售的全流程深度解析

前言 随着人工智能&#xff08;AI&#xff09;技术的迅速发展&#xff0c;AI已经渗透到各个行业&#xff0c;尤其是在销售领域&#xff0c;AI正通过智能化外呼系统与CRM&#xff08;客户关系管理&#xff09;系统的结合&#xff0c;极大提升了企业的销售效率。在这篇文章中&am…

C++动态规划及九种背包问题

目录 目录 一&#xff0c;动态规划 一&#xff09;&#xff0c;动态规划的定义 二&#xff09;&#xff0c;动态规划其他的相关概念&#xff08;也是使用条件&#xff09; 1&#xff0c;重叠子问题 2&#xff0c; 最优子结构 3&#xff0c;无后效性 三&#xff09;&…