diff --git a/Leecode/src/main/java/com/markilue/leecode/dynamic/T21_MaxProfit.java b/Leecode/src/main/java/com/markilue/leecode/dynamic/T21_MaxProfit.java new file mode 100644 index 0000000..be80101 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/dynamic/T21_MaxProfit.java @@ -0,0 +1,209 @@ +package com.markilue.leecode.dynamic; + +import org.junit.Test; + +/** + * @BelongsProject: Leecode + * @BelongsPackage: com.markilue.leecode.dynamic + * @Author: dingjiawen + * @CreateTime: 2022-12-12 10:02 + * @Description: + * TODO 力扣121题 买卖股票的最佳时期: + * 给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 + * 你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。 + * 返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。 + * @Version: 1.0 + */ +public class T21_MaxProfit { + + @Test + public void test(){ + int[] price= {7, 1, 5, 3, 6, 4}; + System.out.println(maxProfit1(price)); + } + + @Test + public void test1(){ + int[] price= {7,6,4,3,1}; + System.out.println(maxProfit1(price)); + } + + /** + * 思路:第一反应还是一个背包问题:即在第i天所能获得的最大——但似乎是一个O(n^2)复杂度 + * TODO 动态规划五部曲: + * (1)dp定义:dp[i][j]表示在第i天买入在第j天的最大利润 + * (2)dp状态转移方程:dp[i][j]=max(dp[i][j-1],prices[j]-prices[i],dp[i-1][j]) + * (3)dp初始化:dp[i][j<=i]=0 + * (4)dp遍历顺序: + * (5)dp举例推导: + * 到第198个案例后超出内存限制 + * @param prices + * @return + */ + public int maxProfit(int[] prices) { + if(prices.length==1){ + return 0; + } + + int[][] dp = new int[prices.length][prices.length]; + + for (int i = 1; i < prices.length; i++) { + dp[0][i]=Math.max(dp[0][i-1],prices[i]-prices[0]); + } + + + for (int i = 1; i < prices.length; i++) { + for (int j = i+1; j < prices.length; j++) { + dp[i][j]=Math.max(Math.max(dp[i][j-1],prices[j]-prices[i]),dp[i-1][j]); + } + } + + return dp[prices.length-2][prices.length-1]; + + } + + + /** + * 思路:尝试一遍遍历,思路,遇见负数就将现在的值置为最小值,本质上是一种贪心 + * 速度击败55.26%,内存击败71.51% 2ms + * @param prices + * @return + */ + public int maxProfit1(int[] prices) { + if(prices.length==1){ + return 0; + } + + int max=0; + int min=prices[0]; + + for (int i = 1; i < prices.length; i++) { + if(prices[i] maxprofit) { + maxprofit = prices[i] - minprice; + } + } + return maxprofit; + } + + /** + * 评论区从右往左动态规划法:比较巧妙 + * 巧妙在于:当天(i)的买入最大利润=i+1天以后的最大值-当天的价格 + * 而i+1天以后的最大值=i+1天的利润+(i+1)时的股票价格 + * 所以当天利润=i+1天的利润+(i+1)时的股票价格-当天的价格 + * 速度击败30.8%,内存击败98% + * @param prices + * @return + */ + public int maxProfit3(int[] prices) { + int len=prices.length; + int[] dp=new int[len];//每天买入股票对应能获取的最大利润 + int max=0;//最大利润 + dp[len-1]=0;//最后一天买入的话利润为0 + for(int i=len-2;i>=0;i--){ + //因为利润最大的话,当然是选择价格最高的时候卖出 + //所以dp[i+1]=(第i+1天之后最高价格)- prices[i+1]; + //如果dp[i+1]=0,那么第i天之后最高价格只可能是prices[i+1] + //如果dp[i+1]>0就是dp[i+1]+prices[i+1]; + //所以第i天买入能获得的最大利润就是profit + //如果第i天价格比之后都高的话,那么profit<0 + int profit=dp[i+1]+prices[i+1]-prices[i]; + dp[i]=profit>0 ? profit : 0; + max=profit>max ? profit : max; + } + return max; + } + + /** + * 代码随想录动态规划法(版本一): + * TODO 动态规划五部曲: + * (1)dp定义:dp[i][0]表示第i天持有股票的最多现金;dp[i][1]表示第i天不持有股票的最多现金 + * (2)dp状态转移方程: + * 1.dp[i][0] 注意:题目规定只能买入一次,所以有下面的推导 + * dp[i][0]表示当天持有,所以可以从两个情况获得:昨天就持有了;或者今天才买入 + * dp[i][0]=max(dp[i-1][0],-price[i]) + * 2.dp[i][1] + * dp[i][1]表示当天不持有,所以也可以从两种情况获得:昨天不持有;或者今天才卖出 + * dp[i][1]=max(dp[i-1][1],price[i]+dp[i-1][0]) + * (3)dp初始化:dp[0][0]=-price[0];dp[0][1]=0 + * (4)dp遍历顺序:从状态转移方程可以看出从前往后 + * (5)dp距离推导:输入:[7,1,5,3,6,4]为例,dp数组状态如下: + * [0,1] + * i=0 -7 0 + * i=1 -1 0 + * i=2 -1 4 + * i=3 -1 4 + * i=4 -1 5 + * i=5 -1 5 + * 速度击败6.7%,内存击败89.24% + * @param prices + * @return + */ + public int maxProfit4(int[] prices) { + if (prices == null || prices.length == 0) return 0; + int length = prices.length; + // dp[i][0]代表第i天持有股票的最大收益 + // dp[i][1]代表第i天不持有股票的最大收益 + int[][] dp = new int[length][2]; + int result = 0; + dp[0][0] = -prices[0]; + dp[0][1] = 0; + for (int i = 1; i < length; i++) { + dp[i][0] = Math.max(dp[i - 1][0], -prices[i]); + dp[i][1] = Math.max(dp[i - 1][0] + prices[i], dp[i - 1][1]); + } + return dp[length - 1][1]; + } + + /** + * 代码随想录动态规划法(版本二):滚动数组,记录前一天的即可 + * 速度击败55.26%,内存击败16.18% + * @param prices + * @return + */ + public int maxProfit5(int[] prices) { + int[] dp = new int[2]; + // 记录一次交易,一次交易有买入卖出两种状态 + // 0代表持有,1代表卖出 + dp[0] = -prices[0]; + dp[1] = 0; + // 可以参考斐波那契问题的优化方式 + // 我们从 i=1 开始遍历数组,一共有 prices.length 天, + // 所以是 i<=prices.length + for (int i = 1; i <= prices.length; i++) { + // 前一天持有;或当天买入 + dp[0] = Math.max(dp[0], -prices[i - 1]); + // 如果 dp[0] 被更新,那么 dp[1] 肯定会被更新为正数的 dp[1] + // 而不是 dp[0]+prices[i-1]==0 的0, + // 所以这里使用会改变的dp[0]也是可以的 + // 当然 dp[1] 初始值为 0 ,被更新成 0 也没影响 + // 前一天卖出;或当天卖出, 当天要卖出,得前一天持有才行 + dp[1] = Math.max(dp[1], dp[0] + prices[i - 1]); + } + return dp[1]; + } + +} diff --git a/Leecode/src/main/java/com/markilue/leecode/dynamic/T22_MaxProfit.java b/Leecode/src/main/java/com/markilue/leecode/dynamic/T22_MaxProfit.java new file mode 100644 index 0000000..1acd6ab --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/dynamic/T22_MaxProfit.java @@ -0,0 +1,96 @@ +package com.markilue.leecode.dynamic; + +import org.junit.Test; + +/** + * @BelongsProject: Leecode + * @BelongsPackage: com.markilue.leecode.dynamic + * @Author: dingjiawen + * @CreateTime: 2022-12-12 12:23 + * @Description: + * TODO 力扣122题 买卖股票的最佳时机 II: + * 给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。 + * 在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。 + * 返回 你能获得的 最大 利润 。 + * @Version: 1.0 + */ +public class T22_MaxProfit { + + @Test + public void test(){ + int[] prices = {7, 1, 5, 3, 6, 4}; + System.out.println(maxProfit(prices)); + } + + @Test + public void test1(){ + int[] prices = {1,2,3,4,5}; + System.out.println(maxProfit(prices)); + } + + /** + * 思路:这题实际上在贪心的时候遇到过,这里使用动态规划法: + * TODO 动态规划法: + * (1)dp定义:dp[i][0]表示当天不留股票的最大收益;dp[i][1]表示当天留股票的最大收益 + * (2)dp状态转移方程: + * 1.dp[i][0]=昨天不留+今天也不留 ;昨天留了今天卖了 + * dp[i][0]=max(dp[i-1][0],dp[i-1][1]+price[i]) + * 2.dp[i][1]=昨天留了今天没买;昨天没留今天买了 + * dp[i][1]=max(dp[i-1][1],dp[i-1][0]-price[i])) + * (3)dp初始化:dp[0][0]=0;dp[0][1]=-price[0] + * (4)dp遍历顺序:从前往后 + * (5)dp举例推导:以prices = [7,1,5,3,6,4]为例: + * [0 1] + * i=0 0 -7 + * i=1 0 -1 + * i=2 4 -1 + * i=3 4 1 + * i=4 7 1 + * i=5 7 3 + * 速度击败24.78%,内存击败28.24% 3ms + */ + public int maxProfit(int[] prices) { + int[][] dp = new int[prices.length][2]; + dp[0][0]=0; + dp[0][1]=-prices[0]; + + for (int i = 1; i < prices.length; i++) { + dp[i][0]=Math.max(dp[i-1][0],dp[i-1][1]+prices[i]); + dp[i][1]=Math.max(dp[i-1][1],dp[i-1][0]-prices[i]); + } + return dp[prices.length-1][0]; + + } + + /** + * 对上述思路进行简化:使用滑动数组记录上次 + * 速度击败82.36%,内存击败66.41% + */ + public int maxProfit1(int[] prices) { + int dp0=0; + int dp1=-prices[0]; + + for (int i = 1; i < prices.length; i++) { + dp0=Math.max(dp0,dp1+prices[i]); + dp1=Math.max(dp1,dp0-prices[i]); + } + return dp0; + + } + + + /** + * 官方贪心法:本质上就是利润累加 + * @param prices + * @return + */ + public int maxProfit2(int[] prices) { + int ans = 0; + int n = prices.length; + for (int i = 1; i < n; ++i) { + ans += Math.max(0, prices[i] - prices[i - 1]); + } + return ans; + } + +}