解锁动态规划的奥秘:从零到精通的创新思维解析(10)

解锁动态规划的奥秘:从零到精通的创新思维解析(10)

前言:

        前几天,我写了一篇关于动态规划的文章,今天继续为大家带来一些动态规划相关的习题解析。本次分享的两道题依然围绕“股票”问题展开,不过相比之前的题目,难度有所提升。希望能为大家的学习提供帮助!

1.买卖股票的最佳时机

1.1.题目来源

        本题目来源于力扣,下面小编给出它的链接:121. 买卖股票的最佳时机 - 力扣(LeetCode)

1.2.题目解析

        本题是小编之前讲解的股票问题的升级版。它实际上是一个经典的股票问题,因为在这一版本中,既没有交易手续费,也没有冷却期。问题的状态很直观:分为买入和卖出两种状态。不过,与之前的版本不同的是,本题对交易次数有限制——我们只能进行一次交易。也就是说,我们需要找到最佳的两天进行买入和卖出操作,从而获得最大的利润。

        本题的难点在于如何高效地找到理论上的最大利润。接下来,小编将详细讲解本题的解题思路。

1.3.思路解析

1.状态表示

        对于动态规划类型的题目,我们通常都需要设置好dp表来帮助我们进行状态的分析,本题小编将会使用两个二维的dp表来表示出本题的两个状态,首先这两个状态方程分别表示买入状态和卖出状态,行号代表着第几天,列号代表着第几次;这样我们通过一个二维的数组就可以表示出本题目的状态,其状态的详细表示如下所示。

f[i][j]; //到达第i天的时候,完成了j次交易,进入了买入状态,此时的最大利润。 g[i][j]; //到达第i天的时候,完成了j次交易,进入了卖出状态,此时的最大利润
2.状态转换方程

        此时我们就可以根据状态进行状态方程的书写了,对于买卖股票的题目,小编认为通过书写状态机来表示出状态转换方程是比较不错的方法,状态机的介绍我在之前的文章写过,想要了解的读者可以直接跳到那篇文章:解锁动态规划的奥秘:从零到精通的创新思维解析(9)-ZEEKLOG博客,下面我就带领各位一步一步的书写状态机。

首先我们先表示出两种状态:

之后我们知道前一天是买入状态的话,第二天可以仍然手里攥着股票,或者是把股票卖出去(此时就是代表着交易的次数-1):

如果前一天是卖出状态的话,那么第二天仍然可以不买股票,或者是选择买入股票:

        以上就是状态机的表示方法,可能很多读者朋友会奇怪,为什么我会选择在卖出的时候才让交易次数+1,因为我的理解是:一次完整的交易应当是包含买入和卖出,仅仅有其中一个是不够的,可以选择在买入的时候就让交易次数的+1,也可以和我一样,在卖出的时候选择让交易次数+1,这个全凭个人的喜好。通过上面的状态机,我们完全可以写出状态转换方程,此时需要借助最大值函数:

f[i][j] = max(f[i-1][j],g[i-1][j]-price[i]); g[i][j] = max(g[i-1][j],f[i-1][j-1] + price[i]);  //这个写法其实有错误,具体的可以看下面的代码部分,埋下一个小小的伏笔
3.初始化

        对于第一行的数据,我们需要进行一次初始化(这里我就不使用虚拟节点了),对于f [0] [0],肯定是-price[0],因为第一天是买入状态的话,只能买当天的股票;g[0] [0]只有可能是0,;而f[0] [1]和g[0] [1],为了不让他影响结果,统一让它为-0x3f3f3f3f(比最小值大一点,如果用最小值的话过不了),具体如下所示:

f[0][0] = -price[0]; g[0][0] = 0; f[0][1] = g[0][1] = -0x3f3f3f3f;
4.填表顺序

        肯定还是从左到右,从上到下【这里很好判断,如果需要有用到之前的数据,那就是从左到右/从上到下,如果用到之后的数据,那自然就是反着来了】。

5.返回值

        返回g[n - 1] [j]中的最大项(n表示一共有多少天),这里肯定有读者朋友问了,那么我的f去哪了?这里只要各位捋清楚一件事:这股票是你攥在手上利润大,还是在最佳时机卖掉利润大?

1.4.代码书写

        此时,我们需要先把状态转换方程设置好(两个表),并且根据我上面所说进行初始化操作:

int n = prices.size();  //一共多少天 vector<vector<int>> f(n,vector<int>(2));//买入状态 vector<vector<int>> g(n,vector<int>(2));//卖出状态 f[0][0] = -prices[0],f[0][1] = g[0][1] = -0x3f3f3f3f;

        之后,我们就要进入状态转换方程的书写了,这里我要收回一个伏笔,上面我在状态转换方程的时候曾经说过,g表的书写是错误的,这是因为这里牵扯到了j,因为当j等于0的时候(就比如g[1] [0]),如果此时要卖出股票了,但是此时根据状态方程,我们就要取到g[0] [-1],这个就是经典的错误,标准的零分,此时我们就需要处理这种情况,废话不多说,我的解决方案是:我们每次填g表的时候,先让其为之前是啥也不干的状态,此时判断g-1是否大于等于0,如果符合这个情况,我们在和买入状态进行最大值的取舍,具体写法如下所示:

for(int i = 1 ; i < n ; i++) {    for(int j = 0 ; j < 2 ; 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]);   } }

        这里我不得不感慨一下,算法题真的是坑多,一不小心就会掉入一个坑里,有时候掉到坑里可能我们都不自知,所以这就告诫我们,做任何的习题,我们都要细心细心再细心!

        之后我们直接通过循环(此题不需要)找到最大值即可:

return max(g[n - 1][0],g[n - 1][1]);

1.5.总代码展示

class Solution { public:    int maxProfit(vector<int>& prices) {        int n = prices.size();        vector<vector<int>> f(n,vector<int>(2));//买入状态        vector<vector<int>> g(n,vector<int>(2));//卖出状态        f[0][0] = -prices[0],f[0][1] = g[0][1] = -0x3f3f3f3f;        for(int i = 1 ; i < n ; i++)       {            for(int j = 0 ; j < 2 ; 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]);           }       }        return max(g[n - 1][0],g[n - 1][1]);   } };

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

2.1.题目来源

        老规矩,我先把题目的链接发一下:123. 买卖股票的最佳时机 III - 力扣(LeetCode)

        本题其实和上题比较类似,但还是有一些区别的,就比如上面的这个题目,告诉我们最多可以完成两笔交易,而第一个题目我们仅仅就可以完成一次交易,所以其实仔细一看这俩题目简直就是同根生(这里我继续留下一个伏笔,下个题目就会揭晓为什么他俩这么像~)。

2.3.思路解析

1.状态表示

        和上个题一样,这个题目我们也是需要用到两个表来进行状态表示,分别表示“买入”状态和“卖出”状态。同样的,它们也均是一个二维数组,行号代表着第几天;列号代表着完成了几次交易,合起来就是:在第i天完成了j次交易后的最大利润。

f[i][j]; //到达第i天的时候,完成了j次交易,进入了买入状态,此时的最大利润。 g[i][j]; //到达第i天的时候,完成了j次交易,进入了卖出状态,此时的最大利润
2.状态转换方程

        对于求解状态转换方程,对于买卖股票的问题,我推荐还是使用状态机来完成方程的构建,对于什么是状态机,在我之前的博客亦有记载:解锁动态规划的奥秘:从零到精通的创新思维解析(9)-ZEEKLOG博客,感兴趣的读者可以点击来看一下,里面描述的还是比较详细的,下面废话不多说,开始进入状态机的书写。

首先我们先表示出两种状态:

之后我们知道前一天是买入状态的话,第二天可以仍然手里攥着股票,或者是把股票卖出去(此时就是代表着交易的次数-1):

如果前一天是卖出状态的话,那么第二天仍然可以不买股票,或者是选择买入股票:

        可能很多完整读完了本篇文章的读者会很奇怪:主包主包,上面的状态机不是在第一个题目就写了吗?这里我统一回复下,是这样滴,因为考虑到可能有的朋友点开我的文章是为了本题目,本来我想偷懒简写的,想了想,不如把相同的直接复制,这样直接节省了时间。方便了读者,也方便了小编(绝对不是因为懒[○・`Д´・ ○])

        表示完状态机以后,我们就可以进行状态转换方程的书写了:可以直接根据上面的图片直接进行方程的填入,我相信聪明的你会直接写出来滴:

f[i][j] = max(f[i-1][j],g[i-1][j]-price[i]); g[i][j] = max(g[i-1][j],f[i-1][j-1] + price[i]);  //这个写法其实有错误,具体的可以看下面的代码部分,埋下一个小小的伏笔
3.初始化

        对于本题的初始化其实还是比较容易的,不过此时我们需要借助循环的知识,因为按照之前的买卖股票题,我们仅需填充f[0] [0]以及g[0] [0]即可,本题不太一样,此时我们需要把当我们处于第一天(对应着的是数组的0下表位置),此时我们肯定是不需要进行多次交易的,所以此时我们需要把j为1,2的位置填充上一个比较小的数,这里为了不会在运行的时候实例出错,我的建议就是使用:-0x3f3f3f3f来进行初始化即可。

4.填表顺序

        肯定还是从左到右,从上到下【这里很好判断,如果需要有用到之前的数据,那就是从左到右/从上到下,如果用到之后的数据,那自然就是反着来了】。

5.返回值

        返回g[n - 1] [j]中的最大项(n表示一共有多少天),这里肯定有读者朋友问了,那么我的f去哪了?这里只要各位捋清楚一件事:这股票是你攥在手上利润大,还是在最佳时机卖掉利润大?

2.4.代码书写

        此时我们需要创建好两个表,并且根据我给定的初始化条件,对这两个表进行初始化处理。

int n = prices.size(); vector<vector<int>> f(n,vector<int>(3)); //表示的是买入状态第二个表示此时的次数 vector<vector<int>> g(n,vector<int>(3)); //表示的是卖出状态第二个表示此时的次数 f[0][0] = -prices[0],g[0][0] = 0;

        之后我们就要循环进行往表里填充数据了,不过此时填表的时候,可能会出现一个大坑:当我们处于“卖出”状态的第一天的时候,我们需要选取当前的利润的最大值,此时当我们假设前一天为买入状态的时候,此时我们的j为-1,这样的话就会出现经典的错误,标准的零分,此时我们仅需做一个判断就可以解决这个问题了:此时我们在卖出的状态的时候,假设前一天依旧为卖出状态,我们判断j是否大于0,如果大于0的话,那么就和前一天的买入状态进行比较最大值,这样的话我们就把这个坑解决了。之后我们通过两次循环即可。

for(int j = 1 ; j < 3 ; j++) { f[0][j] = -0x3f3f3f3f; g[0][j] = -0x3f3f3f3f; //减少数太小导致负极必反的情况 } 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-1][j],f[i-1][j-1] + prices[i]); } }

        之后直接返回最终的最大值即可。

return max(g[n-1][0],max(g[n-1][1],g[n-1][2]));

2.5.总代码展示

class Solution { public:    int maxProfit(vector<int>& prices) {        int n = prices.size();        vector<vector<int>> f(n,vector<int>(3)); //表示的是买入状态第二个表示此时的次数        vector<vector<int>> g(n,vector<int>(3)); //表示的是卖出状态第二个表示此时的次数        f[0][0] = -prices[0],g[0][0] = 0;        for(int j = 1 ; j < 3 ; j++)       {            f[0][j] = -0x3f3f3f3f;            g[0][j] = -0x3f3f3f3f;  //减少数太小导致负极必反的情况       }        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-1][j],f[i-1][j-1] + prices[i]);           }       }        return max(g[n-1][0],max(g[n-1][1],g[n-1][2]));   } };

3.总结

        在股市的江湖里,如何用两次交易抓住财富密码?这篇动态规划攻略堪称《韭菜的自我修养》plus pro max版!我们用f数组表示"此刻必须装深沉"的持股市态,用g数组演绎"落袋为安后假装淡定"的卖出入圣。状态转移就像在贪婪与恐惧之间反复横跳,交易次数限制仿佛在说:"年轻人,耗子尾汁"。

        最后在三种交易次数的结局中选最大收益时,像极了成年人表示:"我全都要!"。从此炒股代码写得比大妈广场舞还丝滑,收益算得比算命先生还精准。各位代码修仙的大佬们,我们下期在算法的世界里继续御剑飞行,保不准哪天就能用动态规划算出桃花运呢!(手动狗头)。

        五一劳动节到啦,祝各位代码打工人键盘敲出火花,摸鱼摸出境界,搬砖搬成架构师!愿你们的程序无bug,需求不改稿,调休不调休都开心(反正AI不过节,但人类得快乐啊)~ 咱们下篇文章继续用算法征服世界,毕竟——劳动(coding)最光荣!(狗头保命撤退)

Read more

Microsoft Edge WebView2 Runtime(运行库)快速部署 + 调试指南(精简实用、适配开发 + 用户双场景)

Microsoft Edge WebView2 Runtime(运行库)快速部署 + 调试指南(精简实用、适配开发 + 用户双场景)

WebView2运行库 v143.0.3650.139 x64 精简安装(下载) 一、WebView2 Runtime 快速安装部署(用户 / 开发通用,必做) ✅ 1. 系统预装情况 ▸ Windows 11 系统 默认自带 常青版 WebView2 运行库,无需手动安装;▸ Windows 10/7/8.1 需手动安装,缺失则调用 WebView2 控件的软件会弹窗报错「缺少 WebView2 运行环境」。 ✅ 2. 两种官方安装方式(推荐) 方式 1:常青版(Evergreen Runtime)- 首选 ▸ 特点:体积小(引导包仅

By Ne0inhk

解密微信视频号WebAssembly加密:从逆向到实现的完整指南

解密微信视频号WebAssembly加密:从逆向到实现的完整指南 最近在研究一些视频平台的资源获取方式时,不可避免地遇到了微信视频号。和许多开发者一样,最初的想法是寻找一个现成的工具,比如在GitHub上颇有名气的WeChatVideoDownloader。它的代理思路很巧妙,但很快我就发现,直接下载下来的视频文件打不开了——文件头不对劲,播放器完全不认。这显然不是网络问题,而是视频数据本身被动了手脚。微信给视频号内容加上了一层加密,这对于想要深入研究其技术实现,或者有合法合规的离线分析需求的开发者来说,成了一个必须跨过的门槛。这篇文章,就是记录我如何一步步拆解这层加密外壳,并最终实现完整解密流程的旅程。整个过程涉及对前端JavaScript的调试、对WebAssembly模块的逆向分析,以及对特定随机数生成算法的理解,目标读者是那些对WebAssembly、加密算法和浏览器逆向有浓厚兴趣,并愿意动手实践的技术爱好者。 1. 现象探查与加密特征分析 当你从视频号下载一个视频文件,用十六进制编辑器打开它的头部,第一眼就会发现问题。一个正常的MP4文件,其文件头通常以清晰的ftyp

By Ne0inhk

2026 前端新手必装 VS Code 插件|10 个插件提升开发效率(附配置教程)

2026 前端新手必装 VS Code 插件|10 个插件提升开发效率(附配置教程) VS Code 作为前端开发的「宇宙第一编辑器」,轻量性与强大的插件生态是其核心优势。对新手而言,选对插件能省去重复操作、减少语法错误,让编码效率翻倍。本文精选 10 个高频插件,按「代码高亮/格式化/快捷键辅助」分类,逐一拆解功能、安装及配置步骤,再分享组合使用技巧与冲突解决方法,帮你快速搭建高效开发环境。 一、插件分类与精选推荐 前端开发的核心场景离不开代码识别、格式规范与操作简化,本次推荐插件严格围绕这三大维度,兼顾新手友好度与实用性,避免冗余插件增加学习成本。 (一)代码高亮类:提升代码可读性 这类插件优化语法着色与文件识别,让不同语言、不同类型文件直观区分,降低视觉疲劳,尤其适合长时间编码。 1. One Dark Pro(经典深色主题) 核心功能:

By Ne0inhk
使用Open WebUI下载的模型文件(Model)默认存放在哪里?

使用Open WebUI下载的模型文件(Model)默认存放在哪里?

🏡作者主页:点击!  🤖Ollama部署LLM专栏:点击! ⏰️创作时间:2025年2月21日21点21分 🀄️文章质量:95分 文章目录 使用CMD安装存放位置 默认存放路径 Open WebUI下载存放位置 默认存放路径 扩展知识 关于 Ollama 核心价值 服务 关于Open WebUI 核心特点 主要功能 使用场景 Open WebUI下载存放位置 在使用Ollama平台进行深度学习和机器学习模型训练时,了解模型文件的存储位置至关重要。这不仅有助于有效地管理和部署模型,还能确保在需要时能够快速访问和更新这些模型文件。本文将详细探讨Ollama下载的模型文件存放在哪里,并提供相关的操作指南和最佳实践 最后感谢大家 希望这篇文章能帮助你! 使用CMD安装存放位置 以下做测试 我们采用哦llama38B模型来测试 输入命令等待安装即可 默认存放路径 C:\Users\Smqnz\.ollama\models\manifests\registry.ollama.ai 不要直接复制粘贴 我的用户名和你的不一样

By Ne0inhk