Leetcode|有限状态自动机团灭买卖股票6道题

2024-01-10 03:48

本文主要是介绍Leetcode|有限状态自动机团灭买卖股票6道题,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

0 前言及问题描述

大三编译原理中的确定有限状态自动机DFA,其实是动态规划中的一大杀器。我们知道,动态规划中最难之一的是写出状态转移方程,而导致状态转移的根本正是题目中的选择,因此,只要我们根据选择列表绘出对应状态的有限自动机,即可找到写出状态转移方程的突破口。感谢labuladong公众号的讲解,以下以力扣的买卖股票问题为例,加以总结

在这里插入图片描述

1 有限状态自动机的使用

我们根据题目列出以下四个状态,但只有前三个状态的上下界是题目已知的,第四个状态的上界未知,因此我们只能使用前三个状态作为dp数组索引,将第四个状态作为值

【状态】:①天数(数组索引i);②剩余交易次数k;③是否持有股票10④累计利润(待求)

【选择】:①在第i天买股票;②在第i天卖股票;③第i天不交易

由于选择决定状态如何转移,因此,我们从选择入手,找到不同选择会改变的状态为②剩余交易次数k和③是否持有股票10,这里,画出选择和状态③之间有限状态自动机

在这里插入图片描述
有了状态自动机,我们即可快速找到状态转移关系,设选择列表为 A = { b u y , s e l l , r e s t } A = \{buy, sell, rest\} A={buy,sell,rest},状态集为 S = { s 0 , s 1 } S = \{s_0, s_1\} S={s0,s1},每个状态的价值(累计利润)为 V ( s ) V(s) V(s),每个状态执行某个动作后的价值为 Q ( s , a ) Q(s,a) Q(s,a),则有
V ( s 0 ) = max ⁡ { Q ( s 0 , r e s t ) , Q ( s 1 , s e l l ) } V ( s 1 ) = max ⁡ { Q ( s 1 , r e s t ) , Q ( s 0 , b u y ) } V(s_0) = \max \{Q(s_0, rest), Q(s_1, sell)\} \\ V(s_1) = \max \{Q(s_1, rest),Q(s_0, buy)\} V(s0)=max{Q(s0,rest),Q(s1,sell)}V(s1)=max{Q(s1,rest),Q(s0,buy)}

2 股票问题框架

有了以上思路整出框架啦

2.1初始化

  1. 通用初始化全为0
  2. 不符合条件的状态对应值为负无穷①第0天持有股票的状态 + ②无剩余股票交易次数时还持有股票
dp[0][1...K][1] = INT_MIN;
dp[1...i][0][1] = INT_MIN;

2.3 总体框架

dp数组含义】第i天剩余交易数为k下持有股票的累计最大收益为dp[i][k][1],未持有股票为dp[i][k][0]

class Solution {
public:int maxProfit(vector<int>& prices, int K) {int size = prices.size();// dp数组含义:第i天剩余交易数为k下持有股票的累计最大收益为dp[i][k][1],未持有股票为dp[i][k][0]vector<vector<vector<int>>> dp(size + 1, vector<vector<int>> (K + 1, vector<int> (2, 0)));// 边界初始化dp[0][1...K][1] = INT_MIN;dp[1...i][0][1] = INT_MIN;for (int i = 1; i <= size; i++) for (int k = 1; k <= K; k++) {dp[i][k][0] = max(dp[i - 1][k][0], dp[i - 1][k][1] + prices[i - 1]);dp[i][k][1] = max(dp[i - 1][k][1], dp[i - 1][k - 1][0] - prices[i - 1]);}return dp[size][K][0];}
};

① 买卖股票的最佳时机( k = 1 k = 1 k=1

在这里插入图片描述

1.1 股票三维DP框架照搬(885ms)

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

由于第二个for循环只有1次,进一步可修改为

for (int i = 1; i <= size; i++) {dp[i][1][0] = max(dp[i - 1][1][0], dp[i - 1][1][1] + prices[i - 1]);dp[i][1][1] = max(dp[i - 1][1][1], dp[i - 1][0][0] - prices[i - 1]);
}

由于状态转移方程中没有改变dp[i][0][0]的量,因此有dp[i][0][0] == 0,第二个状态转移可简写为

dp[i][1][1] = max(dp[i - 1][1][1], - prices[i - 1]);

最终得到

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

1.2 优化为二维DP(336ms)

既然k == 1,因此可直接降维到二维

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

1.3 空间复杂度优化为 O ( 1 ) O(1) O(1)(136ms)

进一步观察,可以约掉[i - 1]

class Solution {
public:int maxProfit(vector<int>& prices) {int size = prices.size();int dp_i_0 = 0, dp_i_1 = INT_MIN;for (int i = 0; i < size; i++) {dp_i_0 = max(dp_i_0, dp_i_1 + prices[i]);dp_i_1 = max(dp_i_1, - prices[i]);}return dp_i_0;}
};

在这里插入图片描述

② 买卖股票的最佳时机II( k = ∞ k = \infty k=

在这里插入图片描述

2.1 股票DP框架照搬(12ms)

由于 k = ∞ k = \infty k=,因此可以认为 k → k − 1 k \to k - 1 kk1

dp[i][k][1] = max(dp[i - 1][k][1], dp[i - 1][k - 1][0] - prices[i - 1]);= max(dp[i - 1][k][1], dp[i - 1][k][0] - prices[i - 1]);
p[i][k][0] = max(dp[i - 1][k][0], dp[i - 1][k][1] + prices[i - 1]);

因此,既然全是k,可直接把三维DP降维到二维DP

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

2.2 空间复杂度优化为 O ( 1 ) O(1) O(1)(0ms)

其中dp[i][0]作为第二步计算前会被更新覆盖,找个零时变量存储一下就能解决

class Solution {
public:int maxProfit(vector<int>& prices) {int size = prices.size();int dp_i_0 = 0, dp_i_1 = INT_MIN;for (int i = 1; i <= size; i++) {int tmp = dp_i_0;dp_i_0 = max(dp_i_0, dp_i_1 + prices[i - 1]);dp_i_1 = max(dp_i_1, tmp - prices[i - 1]);}return dp_i_0;}
};

在这里插入图片描述

③ 最佳买卖股票时机含冷冻期( k = ∞ k = \infty k= + 冷冻期)

在这里插入图片描述

3.1 股票DP框架照搬(8ms)

  • 由于 k = ∞ k = \infty k=,因此可以认为 k → k − 1 k \to k - 1 kk1
  • 而多加了1天冷冻期,则对于当天的持有,由①前一天持有;②至少2天前卖出导致未持有,然后今天买入;两者状态转移而成

【状态转移】

dp[i][0]= max(dp[i - 1][0], dp[i - 1][1] + prices[i - 1]);   // 当天未持有
dp[i][1] = max(dp[i - 1][1], dp[i - 2][0] - prices[i - 1]);  // 当天持有

写完状态转移后,由于有dp[i - 2],所以要对dp[0][1], dp[1][0], dp[1][1]初始化,完整代码如下

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

3.2 空间复杂度优化为 O ( 1 ) O(1) O(1)(0ms)

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

在这里插入图片描述

④ 买卖股票的最佳时机含手续费( k = ∞ k = \infty k= + 手续费)

在这里插入图片描述

4.1 股票DP框架照搬(232ms)

  • 由于 k = ∞ k = \infty k=,因此可以认为 k → k − 1 k \to k - 1 kk1
  • 而每交易完1笔,即卖出当前股票后,需要支付一个常数小费

【状态转移】

dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i - 1] - fee);  // 当天未持有
dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i - 1]);        // 当天持有

写完状态转移后,由于有dp[i - 1],所以要对dp[0][1]初始化,完整代码如下

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

4.2 空间复杂度优化为 O ( 1 ) O(1) O(1)(84ms,99%beat)

class Solution {
public:int maxProfit(vector<int>& prices, int fee) {int size = prices.size();int dp_i_0 = 0, dp_i_1 = INT_MIN;for (int i = 0; i < size; i++) {if (prices[i] >= fee) dp_i_0 = max(dp_i_0, dp_i_1 + prices[i] - fee);dp_i_1 = max(dp_i_1, dp_i_0 - prices[i]);}return dp_i_0;}
};

在这里插入图片描述

⑤ 买卖股票的最佳时机 III( k = 2 k = 2 k=2

在这里插入图片描述

5.1 股票三维DP框架照搬(904ms)

  • 由于 k = 2 k = 2 k=2,因此可直接套用三维有限状态机框架

【状态转移】

dp[i][k][0] = max(dp[i - 1][k][0], dp[i - 1][k][1] + prices[i - 1]);      // 当天未持有
dp[i][k][1] = max(dp[i - 1][k][1], dp[i - 1][k - 1][0] - prices[i - 1]);  // 当天持有

写完状态转移后,需要对dp[0][k][1]初始化负无穷,完整代码如下

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

5.2 优化为二维DP(176ms,81%beat)

我们发现状态转移的前一个状态都是dp[i - 1]..,因此可直接降维到二维

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

5.3 空间复杂度优化为 O ( 1 ) O(1) O(1)(132ms,98%beat)

在优化之前,我们先把第二个关于k的for循环展开得到

for (int i = 0; i < size; i++) {dp[1][0] = max(dp[1][0], dp[1][1] + prices[i]);dp[1][1] = max(dp[1][1], dp[0][0] - prices[i]);dp[2][0] = max(dp[2][0], dp[2][1] + prices[i]);dp[2][1] = max(dp[2][1], dp[1][0] - prices[i]);
}

不难发现,dp[k]dp[k-1]只有最后一行第二项才有依赖,其余没有。而不同i下的dp[k]dp[k]均有依赖

所以不能简写成2行,而需要把k展开写,针对dp[1][1]dp[2][1]依然单独初始化负无穷,其余全部初始化为0

class Solution {
public:int maxProfit(vector<int>& prices) {int size = prices.size(), K = 2;int dp_0_0 = 0, dp_1_0 = 0, dp_2_0 = 0, dp_1_1 = INT_MIN, dp_2_1 = INT_MIN;for (int i = 0; i < size; i++) {dp_1_0 = max(dp_1_0, dp_1_1 + prices[i]);dp_1_1 = max(dp_1_1, dp_0_0 - prices[i]);dp_2_0 = max(dp_2_0, dp_2_1 + prices[i]);dp_2_1 = max(dp_2_1, dp_1_0 - prices[i]);}return dp_2_0;}
};

在这里插入图片描述

⑥ 买卖股票的最佳时机 IV( k = K k = K k=K

在这里插入图片描述

6.1 股票三维DP框架照搬(40ms,14%beat)

  • 由于 k = K k = K k=K,因此可直接套用三维有限状态机框架

【状态转移】

dp[i][K][0] = max(dp[i - 1][K][0], dp[i - 1][K][1] + prices[i - 1]);      // 当天未持有
dp[i][K][1] = max(dp[i - 1][K][1], dp[i - 1][K - 1][0] - prices[i - 1]);  // 当天持有

写完状态转移后,需要对dp[0][k][1]初始化负无穷,完整代码如下

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

6.2 优化为二维DP(0ms,100%beat)

我们发现状态转移的前一个状态都是dp[i - 1]..,因此可直接降维到二维

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

在这里插入图片描述

6.3 解决 k → ∞ k\to \infty k可能导致的超内存现象

对于Leetcode的测试用例,都是很小的,但实际大厂笔试时,可能会出 k → ∞ k\to \infty k的案例从而导致潜在超内存现象

解决方法很简单,由于消耗1次k最少需要2天,即当k > size / 2时则判定属于 k → ∞ k\to \infty k情况,此时直接调用之前的 O ( 1 ) O(1) O(1)方法即可,代码如下

class Solution {
public:int maxProfit(int k, vector<int>& prices) {int size = prices.size();// 1.消耗1次k最少需要2天,因此可判断若k是+infty则更简单, O(1)if (k > size / 2) {int dp_0 = 0, dp_1 = INT_MIN;for (int i = 0; i < size; i++) {dp_0 = max(dp_0, dp_1 + prices[i]);dp_1 = max(dp_1, dp_0 - prices[i]);}return dp_0;}// 2.k是常数vector<vector<int>> dp(k + 1, vector<int>(2, 0));for (int j = 0; j <= k; j++) dp[j][1] = INT_MIN;for (int i = 0; i < size; i++)for (int K = 1; K <= k; K++) {dp[K][0] = max(dp[K][0], dp[K][1] + prices[i]);dp[K][1] = max(dp[K][1], dp[K - 1][0] - prices[i]);}return dp[k][0];}
};

至此,完结撒花啦✿✿ヽ(°▽°)ノ✿

参考文献

[1] labuladong. LeetCode 股票买卖问题

这篇关于Leetcode|有限状态自动机团灭买卖股票6道题的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/589442

相关文章

linux进程D状态的解决思路分享

《linux进程D状态的解决思路分享》在Linux系统中,进程在内核模式下等待I/O完成时会进入不间断睡眠状态(D状态),这种状态下,进程无法通过普通方式被杀死,本文通过实验模拟了这种状态,并分析了如... 目录1. 问题描述2. 问题分析3. 实验模拟3.1 使用losetup创建一个卷作为pv的磁盘3.

Java实现状态模式的示例代码

《Java实现状态模式的示例代码》状态模式是一种行为型设计模式,允许对象根据其内部状态改变行为,本文主要介绍了Java实现状态模式的示例代码,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来... 目录一、简介1、定义2、状态模式的结构二、Java实现案例1、电灯开关状态案例2、番茄工作法状态案例

通过prometheus监控Tomcat运行状态的操作流程

《通过prometheus监控Tomcat运行状态的操作流程》文章介绍了如何安装和配置Tomcat,并使用Prometheus和TomcatExporter来监控Tomcat的运行状态,文章详细讲解了... 目录Tomcat安装配置以及prometheus监控Tomcat一. 安装并配置tomcat1、安装

Linux之进程状态&&进程优先级详解

《Linux之进程状态&&进程优先级详解》文章介绍了操作系统中进程的状态,包括运行状态、阻塞状态和挂起状态,并详细解释了Linux下进程的具体状态及其管理,此外,文章还讨论了进程的优先级、查看和修改进... 目录一、操作系统的进程状态1.1运行状态1.2阻塞状态1.3挂起二、linux下具体的状态三、进程的

哈希leetcode-1

目录 1前言 2.例题  2.1两数之和 2.2判断是否互为字符重排 2.3存在重复元素1 2.4存在重复元素2 2.5字母异位词分组 1前言 哈希表主要是适合于快速查找某个元素(O(1)) 当我们要频繁的查找某个元素,第一哈希表O(1),第二,二分O(log n) 一般可以分为语言自带的容器哈希和用数组模拟的简易哈希。 最简单的比如数组模拟字符存储,只要开26个c

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

hdu1565(状态压缩)

本人第一道ac的状态压缩dp,这题的数据非常水,很容易过 题意:在n*n的矩阵中选数字使得不存在任意两个数字相邻,求最大值 解题思路: 一、因为在1<<20中有很多状态是无效的,所以第一步是选择有效状态,存到cnt[]数组中 二、dp[i][j]表示到第i行的状态cnt[j]所能得到的最大值,状态转移方程dp[i][j] = max(dp[i][j],dp[i-1][k]) ,其中k满足c

hdu 3065 AC自动机 匹配串编号以及出现次数

题意: 仍旧是天朝语题。 Input 第一行,一个整数N(1<=N<=1000),表示病毒特征码的个数。 接下来N行,每行表示一个病毒特征码,特征码字符串长度在1—50之间,并且只包含“英文大写字符”。任意两个病毒特征码,不会完全相同。 在这之后一行,表示“万恶之源”网站源码,源码字符串长度在2000000之内。字符串中字符都是ASCII码可见字符(不包括回车)。

状态dp总结

zoj 3631  N 个数中选若干数和(只能选一次)<=M 的最大值 const int Max_N = 38 ;int a[1<<16] , b[1<<16] , x[Max_N] , e[Max_N] ;void GetNum(int g[] , int n , int s[] , int &m){ int i , j , t ;m = 0 ;for(i = 0 ;

POJ 1625 自动机

给出包含n个可见字符的字符集,以下所提字符串均由该字符集中的字符构成。给出p个长度不超过10的字符串,求长为m且不包含上述p个字符串的字符串有多少个。 g++提交 int mat[108][108] ;int matn ;int N ;map<char ,int> to ;//ACconst int maxm = 108 ;const int kin