Python - 深夜数据结构与算法之 DP - 进阶

目录

一.引言

二.经典算法实战

1.House-Robber [198]

2.House-Robber-2 [213]

3.Best-Sell-Time [121]

4.Best-Sell-Time-2 [122]

5.Best-Sell-Time-3 [123]

6.Best-Sell-Time-4 [188]

7.Best-Sell-Time-Coldown [309]

8. Best-Sell-Time-Fee [714]

三.总结


一.引言

前面我们介绍了一部分 DP 动态规划的题目,由于动态规划在算法中算是一大难点,所以我们接下来继续讲解动态规划的相关算法题目。

二.经典算法实战

1.House-Robber [198]

打家劫舍: https://leetcode.cn/problems/house-robber/description/

◆ 题目分析

0-不偷 1-偷

a[i][0] = max(a[i-1][0], a[i-1][1])

a[i][1] = a[i-1][0] + nums[i]

即对于每一个位置 i,其存在两种可能,偷和不偷。如果偷 i,则 a[i-1] 不能偷,所以就是 a[i-1][0],如果不偷 i,则 a[i-1] 可以偷也可以不偷,所以是 max(a[i-1][0], a[i-1][1])。

◆ 动态规划 V1

class Solution(object):def rob(self, nums):""":type nums: List[int]:rtype: int"""dp = []for i in range(len(nums)):dp.append([0, 0])# 初始化状态dp[0][0] = 0dp[0][1] = nums[0]# DP 转移for i in range(1, len(nums)):dp[i][0] = max(dp[i-1][0], dp[i-1][1])dp[i][1] = dp[i - 1][0] + nums[i]return max(dp[-1][0], dp[-1][1])

◆ 动态规划 V2

class Solution(object):def rob(self, nums):""":type nums: List[int]:rtype: int"""if len(nums) == 0: return 0if len(nums) == 1: return nums[0]N = len(nums)dp = [0] * (N+1)dp[0] = nums[0]dp[1] = max(nums[0], nums[1])for i in range(2, N):# 对于第K个房子,要么 [k-1 房子偷,K不偷] 要么 [k-1 不偷,那就是 k-2 的值 + nums[k]]dp[i] = max(dp[i-1], dp[i-2] + nums[i])return dp[N-1]

上面用两个状态 [0][1] 维护,下面我们优化一下使用一个状态维护,这里 a[i] 表示当前能偷到的最大值,注意此时 a[i] 可能被偷了也可能没被偷。

◆ 动态规划 V3

class Solution(object):def rob(self, nums):""":type nums: List[int]:rtype: int"""# 上一个,当前值pre, now = 0, 0for i in nums:pre, now = now, max(pre + i, now)return now

观察上面的递推,我们使用了 o(n) 的复杂度,但其实 n 只取决于 n-1 和 n-2,所以我们可以优化为双指针。 这里运行时间受乐扣后台机器的情况而定,我们主要掌握解题的思路和代码的优化。

2.House-Robber-2 [213]

打家劫舍2: https://leetcode.cn/problems/house-robber-ii/description/ 

◆ 题目分析

与上题唯一不同是第一个房子和最后一个房子连在一起,变成环形了无法同时偷,所以可以转变思路,要么不偷第一个,则求 nums[1:] 的打家劫舍问题,要么不偷最后一个,偷 nums[: len(nums)-1] 的打家劫舍问题。

◆ 动态规划

class Solution(object):def rob(self, nums):""":type nums: List[int]:rtype: int"""N = len(nums)if N == 0:return 0if N == 1:return nums[0]# 偷第一家 不偷最后一家dp = nums[:N-1]pre, now = 0, 0for i in dp:pre, now = now, max(pre + i, now)# 偷最后一家,不偷第一家pre2, now2 = 0, 0dp2 = nums[1:]for i in dp2:pre2, now2 = now2, max(pre2 + i, now2)return max(now, now2)

边界条件加上两次打家劫舍完成。

3.Best-Sell-Time [121]

股票买卖最佳时机: https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/description/ 

◆ 题目分析

记录过去的最小值,然后记录 dp 状态,dp[i] = max(dp[i] - pre_min, 0),即当前卖出能获得的最大值,如果卖了亏钱那就是 0,这里需要维护 dp 数组与 min。

◆ 动态规划 V1

class Solution(object):def maxProfit(self, prices):""":type prices: List[int]:rtype: int"""# dp[i] = max(dp[i] - pre_min, 0)cur_min = prices[0]dp = [0] * len(prices)for i in range(1, len(prices)):dp[i] = max(prices[i] - cur_min, dp[i-1])cur_min = min(cur_min, prices[i])return dp[-1]

◆ 动态规划 V2

class Solution(object):def maxProfit(self, prices):""":type prices: List[int]:rtype: int"""# dp[i] = max(dp[i] - pre_min, 0)cur_min = prices[0]res = 0for i in prices[1:]:res = max(i - cur_min, res)cur_min = min(cur_min, i)return res

上面 dp 浪费了 o(n) 的数组,其实我们只需要知道当前的最大和最小值,构造两个指针即可,比刚才好一些了。

4.Best-Sell-Time-2 [122]

股票买卖最佳时机2: https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii

◆ 题目分析

这个题目相当于上帝视角买卖股票,所以有一个贪心的策略就是只要第二天比第一天高,我们就进行一次交易获利。当然也可以使用动态规划,规划的转移方程可以描述为:

dp[i] = max(dp[i] - dp[i-1], 0) + dp[i-1]

即这次可以获得的金额是上次的加上今天买卖的值,今天买卖不了就 0。

◆ 贪心算法

class Solution(object):def maxProfit(self, prices):""":type prices: List[int]:rtype: int"""reward = 0for i in range(1, len(prices)):reward += max(prices[i] - prices[i-1], 0)return reward

◆ 动态规划 - V1

class Solution(object):def maxProfit(self, prices):""":type prices: List[int]:rtype: int"""N = len(prices)dp = [0] * N# 当前位置的收益等于上一次的收益和本次的正收益for i in range(1, N):dp[i] = max(prices[i] - prices[i-1], 0) + dp[i-1]return dp[-1]

◆ 动态规划 - V2

class Solution(object):def maxProfit(self, prices):""":type prices: List[int]:rtype: int"""# 双指针pre = 0now = 0for i in range(1, len(prices)):now = max(prices[i] - prices[i-1], 0) + prepre = nowreturn now

DP 转移方程只用到上一次的状态,所以使用双指针即可。 

5.Best-Sell-Time-3 [123]

股票最佳买卖时间 3: https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iii

◆ 题目分析

前面的题目只有买与卖即两个状态,这里增加了第二次买第二次卖,所以需要增加第二次买卖。

0-无操作

1-第一次持有

2-第一次卖出

3-第二次持有

4-第二次卖出

dp[i][j] 中 i 表示第 i 天,j 为 [0 - 4] 五个状态,dp[i][j] 表示第 i 天状态 j 所剩最大现金。 

◆ 动态规划 - V1

    def maxProfit(self, prices):"""0-无操作1-第一次持有2-第一次卖出3-第二次持有4-第二次卖出dp[i][j]中 i 表示第 i 天,j 为 [0 - 4] 五个状态,dp[i][j] 表示第 i 天状态 j 所剩最大现金。"""if len(prices) == 0:return 0# 初始化 DP 空间dp = [[0] * 5 for _ in range(len(prices))]# 第一次持有需要花 -p 的钱dp[0][1] = -prices[0]# 第二次持有因为是买了卖再买,所以还是 -pdp[0][3] = -prices[0]for i in range(1, len(prices)):# 无操作dp[i][0] = dp[i - 1][0]# 第一天买入股票,要么是之前的股票还在即 dp[i-1][1] 要么是 i-1 没买,现在买即 dp[i-1][0] - pdp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i])# 第一天卖出股票,要么是之前已经买了即 dp[i-1][2] 要么是 i-1 买了,i 卖了,所以 dp[i-1][1] + pdp[i][2] = max(dp[i - 1][2], dp[i - 1][1] + prices[i])# 第二天买入同理dp[i][3] = max(dp[i - 1][3], dp[i - 1][2] - prices[i])# 第二天卖出同理dp[i][4] = max(dp[i - 1][4], dp[i - 1][3] + prices[i])return dp[-1][4]

对于买卖而言,都有两种状态,一种是维护现状,一种是买卖,这里逻辑比较混乱,还得多理理。

◆ 动态规划 - V2 

class Solution(object):def maxProfit(self, prices):# 买入的低价buy_1, buy_2 = 2147483647, 2147483647# 收益的高点pro_1, pro_2 = 0, 0for p in prices:buy_1 = min(buy_1, p)pro_1 = max(pro_1, p - buy_1)buy_2 = min(buy_2, p - pro_1)pro_2 = max(pro_2, p - buy_2)return pro_2

有点像条件概率的感觉,第二次买卖是在第一次买卖发生的情况下计算的。由于第一次买卖会赚一些钱 pro1,那第二次买的时候就花费的钱就是当前价格减去咱们第一次赚的钱,price2 - pro1, 这样一来,第二次买卖的结果就代表了前两次买卖的总利润,我们求总利润的最大值就可以了。 这个思路真的牛,清晰明了,和大神还是有差距,努力学习 ing ...

6.Best-Sell-Time-4 [188]

股票买卖4: https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iv/

◆ 题目分析

这里和 123 题的区别是卖 2 次和卖 n 次,通过 for 循环实现多次买卖。

◆ 动态规划  

class Solution(object):def maxProfit(self, k, prices):""":type k: int:type prices: List[int]:rtype: int"""k = min(k, len(prices) // 2)buy = [-float("inf")] * (k+1)sell = [0] * (k+1)for p in prices:for i in range(1, k+1):buy[i] = max(buy[i], sell[i-1] - p)sell[i] = max(sell[i], buy[i] + p)return sell[-1]

7.Best-Sell-Time-Coldown [309]

股票买卖时机-冷冻: https://leetcode-cn.com/best-time-to-buy-and-sell-stock-with-cooldown/ 

◆ 题目分析

官方题解写的比较清晰,这里做一次大自然的搬运工。

◆ 动态规划

class Solution(object):def maxProfit(self, prices):"""0 - 目前有一只股票对应的最大收益1 - 目前没有股票,处于冷冻期的最大收益2 - 目前没有股票,不处于冷冻期"""N = len(prices)dp = [[0] * 3 for _ in range(N)]dp[0][0] = -prices[0]dp[0][1], dp[0][2] = 0, 0for i in range(1, N):# 第 i 天有一只股票: A.还拿着上一天的股票 dp[i-1][0] B.昨天没股票且能买 dp[i-1][2] - pricedp[i][0] = max(dp[i-1][0], dp[i-1][2] - prices[i])# 第 i 天处于冷冻期 A.i-1天卖股票了 说明 i-1 天手里必须有股票 -> dp[i-1][0] + pricedp[i][1] = dp[i-1][0] + prices[i]# 第 i 天没股票且非冷冻期 A.昨天也没股票也非冷冻 dp[i-1][2] B.昨天没股票冷冻 dp[i-1][1]dp[i][2] = max(dp[i-1][2], dp[i-1][1])return max(dp[-1])

最后比较时,如果手上还拿着股票即 dp[i][0] 其实是亏得,因为我们没卖钱还花了买股票的钱,所以我们可以只比较 [1]、[2] 的情况即可。 

8. Best-Sell-Time-Fee [714]

股票买卖-手续费: https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-fee/

◆ 题目分析

可以继续套用 [188] 题的思路,只不过每次卖出时增加 fee。

◆ 动态规划

class Solution:def maxProfit(self, prices, fee):buy, sell = -float("inf"), 0for p in prices:buy = max(buy, sell - p - fee)sell = max(sell, buy + p)return sell

三.总结

这一节我们把经典的买卖股票的问题遍历了一遍,主要思路分为两种,一种是遍历所有状态的 dp,例如可以买卖 2 次,则状态有 0123,以此类推。还有固定指针数量,每次直观增减的优化内存方法,这里题目思路很多,博主在 leetcode 上找了两种思路的整理版,上面的题目我们可以感受一下大致的思路,完整的详解可以到下面的讲解学习。

DP 转移: DP 状态转移股票算法全解

指针循环: 指针循环股票算法全解

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

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

相关文章

【React系列】父子组件通信—props属性传值

本文来自#React系列教程:https://mp.weixin.qq.com/mp/appmsgalbum?__bizMzg5MDAzNzkwNA&actiongetalbum&album_id1566025152667107329) 一. 认识组件的嵌套 组件之间存在嵌套关系: 在之前的案例中,我们只是创建了一个组件App&…

基于粒子群算法的参数拟合,寻优算法优化测试函数

目录 摘要 测试函数shubert 粒子群算法的原理 粒子群算法的主要参数 粒子群算法原理 粒子群算法参数拟合 代码 结果分析 展望 基于粒子群算法的参数拟合(代码完整,数据齐全)资源-CSDN文库 https://download.csdn.net/download/abc991835105/88698417 摘要 寻优算法,测试…

后端开发——JDBC的学习(三)

本篇继续对JDBC进行总结: ①通过Service层与Dao层实现转账的练习; ②重点:由于每次使用连接就手动创建连接,用完后就销毁,这样会导致资源浪费,因此引入连接池,练习连接池的使用; …

x-cmd pkg | tig - git 文本模式界面

目录 简介首次用户功能特点类似工具与竞品进一步探索 简介 tig 由 Jonas Fonseca 于 2006 年使用 C 语言创建的 git 交互式文本命令行工具。旨在开启交互模式快速浏览 git 存储库的信息以及 git 命令的运行。 首次用户 使用 x tig 即可自动下载并使用 在终端运行 eval "…

微服务之间互相调用出现的错误

场景: 微服务A调用微服务B的接口,微服务B的接口请求方式是get类型,传递的参数是JSON格式。 错误: 1、postman:springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserial…

Gin 框架介绍与快速入门

Gin 框架介绍与快速入门 文章目录 Gin 框架介绍与快速入门一、Gin框架介绍1. 快速和轻量级2. 路由和中间件3. JSON解析4. 支持插件5. Gin相关文档 二、基本使用1.安装2.导入3.第一个Gin 应用 三、应用举例四、Gin 入门核心1.gin.Engine2.gin.Context 一、Gin框架介绍 Gin是一个…

VitePress搭建Vite官方中文文档首页

✨专栏介绍 在当今数字化时代,Web应用程序已经成为了人们生活和工作中不可或缺的一部分。而要构建出令人印象深刻且功能强大的Web应用程序,就需要掌握一系列前端技术。前端技术涵盖了HTML、CSS和JavaScript等核心技术,以及各种框架、库和工具…

FinGPT——金融领域开源大模型

文章目录 背景论文摘要相关工作大型语言模型(LLMs)和ChatGPT金融领域的LLMs为什么需要开源的金融LLMs? 以数据为中心的方法用于FinLLMs金融数据和独特特性应对处理金融数据的挑战 FINGPT 概述:FINLLM 的开源框架数据来源面向金融N…

【常用排序算法】冒泡排序

冒泡排序 冒泡排序基本思想:N 个数的数组,经过N-1轮排序。 升序 大的值下沉,小的值上浮。降序 小的值下沉,小的字上浮 import java.util.Arrays; public class BubbleSort {public static void main(String[] args) {int[] values…

Git 对项目更新的时候提示错误 repository not owned by current user

遇到 Git 提示的错误信息为:repository not owned by current user 上图显示的是错误的信息。 问题和解决 出现上面错误信息的原因是当前文件夹的权限和 Git 的执行权限不一直导致的。 我们的问题是我们希望在网盘上使用 Git 更新克隆后的代码,但登录…

React Hook 原理,及如何使用Hook

一、 Hook使用规则 只在最顶层使用Hook 不要在循环,条件或嵌套函数中调用Hook; 只在组件函数和自定义hook中调用Hook Q1 : 为什么 hook 不能 在循环,条件或嵌套函数中调用Hook ? A1: 因为这跟React的…

使用Kafka与Spark Streaming进行流数据集成

在当今的大数据时代,实时数据处理和分析已经变得至关重要。为了实现实时数据集成和分析,组合使用Apache Kafka和Apache Spark Streaming是一种常见的做法。本文将深入探讨如何使用Kafka与Spark Streaming进行流数据集成,以及如何构建强大的实…

【智慧地球】星图地球 | 星图地球超算数据工场

当前空天信息处理涉及并发并行的大量计算问题,需要高性能计算、智能计算联合调度,以此来实现多算力融合;而我国算力产业规模快速增长,超算算力资源正需要以任务驱动来统筹。 基于此,中科星图与郑州中心展开紧密合作&a…

从0开始python学习-39.requsts库

目录 HTTP协议 1. 请求 2. 响应 Requests库 1. 安装 2. 请求方式 2.1 requests.请求方式(参数) 2.2 requests.request() 2.3 requests.session().request() 2.4 三种方式之间的关联 3. 请求参数 3.1 params:查询字符串参数 3.2 data:Form表单…

【Python可视化实战】钻石数据可视化

一、项目引言 1.背景和目标 钻石作为一种珍贵的宝石,其价格受到多种因素的影响。为了深入了解钻石价格的决定因素,我们收集了大量关于钻石的数据,并希望通过数据可视化来揭示钻石特征与价格之间的关系。 2.内容 收集钻石的各项特征数据&a…

【大数据】分布式协调系统 Zookeeper

分布式协调系统 Zookeeper 1.Zookeeper 的特点2.Zookeeper 的数据结构3.Zookeeper 的应用场景3.1 统一命名服务3.2 统一配置管理3.3 统一集群管理3.4 服务器动态上下线3.5 软负载均衡 Zookeeper 是 Apache 开源的一个顶级项目,目的是为分布式应用提供协调服务&#…

IO进程线程 day4

进程状态间的转化 创建出三个进程完成两个文件之间拷贝工作&#xff0c;子进程1拷贝前一半内容&#xff0c;子进程2拷贝后一半内容&#xff0c;父进程回收子进程的资源 #include <head.h> int main(int argc, const char *argv[]) {FILE *fp1NULL,*fp2NULL;//定义两个文…

【Java基础篇】常见的字符编码、以及它们的区别

常见的字符编码、以及它们的区别 ✔️ 解析✔️扩展知识仓✔️Unicode和UTF-8有啥关系?✔️有了UTF-8&#xff0c;为什么要出现GBK✔️为什么会出现乱码 ✔️ 解析 就像电报只能发出 ”滴” 和 ”答” 声一样&#xff0c;计算机只认识 0 和 1 两种字符&#xff0c;但是&#x…

【驱动序列】C#获取电脑硬件基本组合以及基础信息

大家好&#xff0c;我是全栈小5&#xff0c;欢迎阅读《小5讲堂之知识点实践序列》文章。 这是2024年第7篇文章&#xff0c;此篇文章是C#知识点实践序列文章&#xff0c;博主能力有限&#xff0c;理解水平有限&#xff0c;若有不对之处望指正&#xff01; 要开发一款驱动小助手&…

vue封装基础input组件(添加防抖功能)

先看一下效果&#xff1a; // 调用页面 <template><div><!-- v-model&#xff1a;伪双向绑定 --><my-input v-model"inputVal" label"姓名" type"textarea" /></div> </template><script> import…