算法日记 33 day 动态规划(打家劫舍,股票买卖)

ops/2024/11/27 3:12:26/

今天来看看动态规划的打家劫舍和买卖股票的问题。

上题目!!!!

题目:打家劫舍

198. 打家劫舍 - 力扣(LeetCode)

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警

给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

题目分析:

        对于每一房间其实只有两种状态,偷或不偷,而且这个状态也取决于之前偷没偷。五部曲分析。

dp[i]:小偷到第i间放的最大金额

i的两种状态(偷,不偷)

不偷:dp[i]=dp[i-1]

偷:dp[i]=dp[i-2]

dp[0]=nums[0]    dp[1]=max(nums[0],nums[1])

public class Solution {public int Rob(int[] nums) {if(nums.Length==0) return 0;if(nums.Length==1) return nums[0];int[] dp=new int[nums.Length];dp[0]=nums[0];dp[1]=Math.Max(nums[0],nums[1]);for(int i=2;i<nums.Length;i++){dp[i]=Math.Max(dp[i-2]+nums[i],dp[i-1]);}return dp[nums.Length-1];}
}

 题目:打家劫舍 2

213. 打家劫舍 II - 力扣(LeetCode)

你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。

给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。

题目分析:

        房子围城一圈,那么对于首尾的房子而言,我们只能偷一个,其他的房间呢则尽量偷。本质上就和上一题一样了,无非是偷的时候划分一下区间即可,其他部分完全一样。

public class Solution {public int Rob(int[] nums) {if(nums.Length==0) return 0;if(nums.Length==1) return nums[0];int res1=RobRange(nums,0,nums.Length-2);int res2=RobRange(nums,1,nums.Length-1);return Math.Max(res1,res2);}public int RobRange(int[] nums,int start,int end){if (end == start) return nums[start];int[] dp=new int[nums.Length];dp[start]=nums[start];dp[start+1]=Math.Max(nums[start],nums[start+1]);for(int i=start+2;i<=end;i++){dp[i]=Math.Max(dp[i-2]+nums[i],dp[i-1]);}return dp[end];}
}

题目:打家劫舍 III

 337. 打家劫舍 III - 力扣(LeetCode)

小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为 root 。

除了 root 之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果 两个直接相连的房子在同一天晚上被打劫 ,房屋将自动报警。

给定二叉树的 root 。返回 在不触动警报的情况下 ,小偷能够盗取的最高金额 。

题目分析: 

        房子的排列变成了树形结构,那么不触发警报意味着,如果我们偷了一个结点,那么他的两个子节点都不能偷。

        其实结点的状态任然是两种偷或者不偷。但是我们要偷取的金额最大,一定要考虑不偷这个的情况下,他的左右子节点偷不偷呢。所以需要将左右孩子的偷不偷的最大金额返回给父节点,那么对于这个树的遍历只能使用后序遍历的方式。

public class Solution {public int Rob(TreeNode root) {int[] res=RobTrue(root);return Math.Max(res[1],res[0]);}public int[] RobTrue(TreeNode root){if(root==null) return new int[2]{0,0};int[] leftDp=RobTrue(root.left);int[] rightDp=RobTrue(root.right);//下标为0记录不偷该节点所得到的的最大金钱,下标为1记录偷该节点所得到的的最大金钱。int res1=root.val+leftDp[0]+rightDp[0];//偷这个结点加上不偷左右孩子的最大值int res2=Math.Max(leftDp[0],leftDp[1])+Math.Max(rightDp[0],rightDp[1]);//不偷这个结点,就考虑左右孩子要不要偷return new int[]{res2,res1};}
}

题目:买卖股票的最佳时机

121. 买卖股票的最佳时机 - 力扣(LeetCode)

给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。

你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。

返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。

题目分析:

        这一题可以用暴力和贪心的方法解决。

暴力,依次枚举股票价格,并且找出最大差值作为结果即可。

贪心的想法很自然就是取最左最小值,取最右最大值,那么得到的差值就是最大利润。

两者差不多。

public class Solution {public int MaxProfit(int[] prices) {int min=int.MaxValue;int res=0;for(int i=0;i<prices.Length;i++){min=Math.Min(prices[i],min);res=Math.Max(res,prices[i]-min);}return res;}
}

动态规划

        考虑股票的状态,其实就只有持有和不持有两种状态。注意这里的持有和不持有并不是买入卖出的意思。来看看dp数组的解释。

dp[i][0]:在第i天这支股票在我手上持有的最大金额(利润)

dp[i][1]:在第i天这支股票不在我手上持有的最大金额(利润)

注意两个的区别,因为这个持有和不持有他需要考虑前一天的状态,也就是说在这一天之前,这个股票我可能已经买入或者卖出了。这个很重要。

递推公式:

持有股票(1.在这一天之前我就持有了 2,之前我没有,但是今天买入后持有了)

1.dp[i-1][0]   2.-price[i]

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

不持有股票(1.在这一天之前我就没有了 2,之前我有,但是今天卖了后就没有了)

1.dp[i-1][1]   2.dp[i-1][0]+price[i]

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

初始化就很好理解了,那么来看看代码

public class Solution {public int MaxProfit(int[] prices) {int[,] dp=new int[prices.Length,2];dp[0,0]=-prices[0];//持有这个股票dp[0,1]=0;//不持有这个股票for(int i=1;i<prices.Length;i++){dp[i,0]=Math.Max(-prices[i],dp[i-1,0]);dp[i,1]=Math.Max(dp[i-1,0]+prices[i],dp[i-1,1]);}return Math.Max(dp[prices.Length-1,0],dp[prices.Length-1,1]);}
}

 题目:买卖股票的最佳时机II

122. 买卖股票的最佳时机 II - 力扣(LeetCode)

给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。

在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。

返回 你能获得的 最大 利润 。

题目分析:

         注意和上一题的区别,题目中的每一天意味着我可以多次买入卖出。但是上一题我只能操作一次。

        这里的dp的含义和上一题一样的,区别是dp的递推公式。

递推公式:

持有股票(1.在这一天之前我就持有了 2,(可能我已经卖出过了)之前我没有,但是今天买入后持有了)

1.dp[i-1][0]   2.dp[i-1][1]-price[i](上一天是不持有的状态)

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

不持有股票(1.在这一天之前我就没有了 2,之前我有,但是今天卖了后就没有了)

1.dp[i-1][1]   2.dp[i-1][0]+price[i]

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

public class Solution {public int MaxProfit(int[] prices) {int[,] dp=new int[prices.Length,2];dp[0,0]=-prices[0];//持有这个股票dp[0,1]=0;//不持有这个股票for(int i=1;i<prices.Length;i++){dp[i,0]=Math.Max(dp[i-1,1]-prices[i],dp[i-1,0]);dp[i,1]=Math.Max(dp[i-1,0]+prices[i],dp[i-1,1]);}return Math.Max(dp[prices.Length-1,0],dp[prices.Length-1,1]);}
}

 主要区别在于持有股票是需要考虑之前是否有卖出过,这一点在下面的题中很重要

题目:买卖股票的最佳时机III

123. 买卖股票的最佳时机 III - 力扣(LeetCode)

给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

 题目分析:

        会看前面两题,第一题要求只能买入卖出1次,第二题能买入卖出无数次。而这一题最多两次,和上面两题一样,看看股票在第i天可能出现的状态。

第一次买入

第一次卖出

第二次买入

第二次卖出

那么dp数组的含义就很好理解了。

来看看递推公式,本质上和上面的题是一样的,无非就是买入卖出的时候需要考虑之前的状态,这一点和第二题一样。

达到dp[i][1]状态,有两个具体操作:

  • 操作一:第i天买入股票了,那么dp[i][1] = dp[i-1][0] - prices[i]
  • 操作二:第i天没有操作,而是沿用前一天买入的状态,即:dp[i][1] = dp[i - 1][1]

那么dp[i][1]究竟选 dp[i-1][0] - prices[i],还是dp[i - 1][1]呢?

一定是选最大的,所以 dp[i][1] = max(dp[i-1][0] - prices[i], dp[i - 1][1]);

同理dp[i][2]也有两个操作:

  • 操作一:第i天卖出股票了,那么dp[i][2] = dp[i - 1][1] + prices[i]
  • 操作二:第i天没有操作,沿用前一天卖出股票的状态,即:dp[i][2] = dp[i - 1][2]

所以dp[i][2] = max(dp[i - 1][1] + prices[i], dp[i - 1][2])

同理可推出剩下状态部分:

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]);

对于初始化而言,需要注意的是你可以在一天内多次买入卖出,因为price的长度可以为1。 

public class Solution {public int MaxProfit(int[] prices) {int[,] dp=new int[prices.Length,5];dp[0,0]=0;//0,不操作dp[0,1]=-prices[0];//1,第一次持有dp[0,2]=0;//2,第一次不持有dp[0,3]=-prices[0];//3,第二次持有dp[0,4]=0;//4,第二次不持有for(int i=1;i<prices.Length;i++){dp[i,1]=Math.Max(dp[i-1,1],-prices[i]);dp[i,2]=Math.Max(dp[i-1,1]+prices[i],dp[i-1,2]);dp[i,3]=Math.Max(dp[i-1,3],dp[i,2]-prices[i]);dp[i,4]=Math.Max(dp[i-1,4],dp[i-1,3]+prices[i]);}return Math.Max(dp[prices.Length-1,4],dp[prices.Length-1,2]);}
}

 

对于更详细的解析与其他语言的代码块,可以去代码随想录上查看。

代码随想录 (programmercarl.com)

已刷题目:110

http://www.ppmy.cn/ops/136973.html

相关文章

安装 Docker(使用国内源)

一、安装Docker-ce 1、下载阿里云的repo源 [rootlocalhost ~]# yum install yum-utils -y && yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo && yum makecache # 尝试列出 docker-ce 的版本 [rootlocalh…

指南: 如何在 MEV 项目中使用 Yul

这对我来说是一个反复出现的故事。我学习了一些 Solidity&#xff0c;发现了一个我想要研究的服务。代码看起来是这样的&#xff1a; Seaport Core: BasicOrderFulfiller.sol Solidity 代码在哪里&#xff1f;人们似乎不再使用普通的 Solidity 代码了 &#x1f972; 这种在智能…

Redis 过期策略和内存淘汰策略

一、过期策略 我们在set key的时候&#xff0c;可以给它设置一个过期时间&#xff0c;比如expire key 60。指定这key60s后过期&#xff0c;60s后&#xff0c;redis是如何处理的&#xff1f; 定时删除&#xff08;Active Expiration&#xff09; 原理&#xff1a;Redis会在后台…

java学习记录12

ArrayList方法总结 构造方法 ArrayList() 构造一个初始容量为 10 的空列表。 ArrayList(int initialCapacity) 构造一个具有指定初始容量的空列表。 实例方法 add(int index, E element) 在此list中的指定位置插入指定元素。 ArrayList<Integer> array…

神经网络问题之:梯度不稳定

梯度不稳定是深度学习中&#xff0c;特别是在训练深度神经网络时常见的一个问题&#xff0c;其本质涉及多个方面。 一、根本原因 梯度不稳定问题的根本原因在于深度神经网络的结构和训练过程中的一些固有特性。随着网络层数的增加&#xff0c;梯度在反向传播过程中会逐层累积变…

《Spring Cloud 微服务》

一、引言 在现代软件开发中&#xff0c;微服务架构已成为主流趋势。Spring Cloud 作为构建微服务架构的强大工具集&#xff0c;提供了一系列组件和解决方案&#xff0c;帮助开发者轻松构建、部署和管理分布式系统。本文将深入介绍 Spring Cloud 的核心概念、主要组件、工作原理…

python画图|无坐标轴自由划线操作fig.add_artist(lines.Line2D()函数

【1】引言 新发现了一种自由划线操作函数&#xff0c;和大家共享。 【2】官网教程 点击下述代码&#xff0c;直达官网&#xff1a; https://matplotlib.org/stable/gallery/misc/fig_x.html#sphx-glr-gallery-misc-fig-x-py 官网代码非常简洁&#xff0c;我进行了解读。 …

fingerprint.js的使用

FingerprintJS 是一个基于 JavaScript 的浏览器指纹识别库&#xff0c;可以通过收集浏览器和设备的多个属性来生成一个独一无二的指纹&#xff08;即一个用户的唯一标识符&#xff09;。这个库的常见用途包括防止欺诈、识别重复用户、分析流量等。 安装 FingerprintJS 1. 通过…