diff --git a/Leecode/src/main/java/com/markilue/leecode/dynamic/T23_MaxProfit.java b/Leecode/src/main/java/com/markilue/leecode/dynamic/T23_MaxProfit.java new file mode 100644 index 0000000..462fca3 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/dynamic/T23_MaxProfit.java @@ -0,0 +1,131 @@ +package com.markilue.leecode.dynamic; + +import org.junit.Test; + +/** + * @BelongsProject: Leecode + * @BelongsPackage: com.markilue.leecode.dynamic + * @Author: dingjiawen + * @CreateTime: 2022-12-13 09:47 + * @Description: + * TODO 力扣123题 买卖股票的最佳时期III: + * 给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。 + * 设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。 + * 注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。 + * @Version: 1.0 + */ +public class T23_MaxProfit { + + @Test + public void test(){ + int[] prices={3, 3, 5, 0, 0, 3, 1, 4}; + System.out.println(maxProfit2(prices)); + } + + + + /** + * 思路:这题的核心差别是:最多可以完成两笔交易,本人没有啥思路,被卡在怎么知道卖出了第一次了,以下是代码随想录解法 + * TODO 动态规划五部曲:核心在于dp的定义和初始化 ->越来越发现,状态转移方程为什么叫状态 转移方程 + * (观察第i天的情况可以发现,第i天可能有五种状态(从来没有操作;已经买了第一次股票;已经卖出了第一次股票;已经买入了第二次股票;已经卖出了第二次股票)) + * (1)dp定义:dp[i][0]第i天从来没有操作获得的最大值;dp[i][1]第i天已经买了第一次股票获得的最大值;dp[i][2]第i天已经卖出了第一次股票获得的最大值,以此类推 + * (2)dp状态转移方程: + * 1.dp[i][0] 从来没有操作 + * dp[i][0]=0 + * 2.dp[i][1] = 昨天就买了第一次了 ;今天才买第一次 + * dp[i][1]=max(dp[i-1][1],-price[i]) + * 3.dp[i][2] = 昨天就卖出了第一次 ;昨天买了第一次的状态,今天才卖 + * dp[i][2]=max(dp[i-1][2],dp[i-1][1]+price[i]) + * 4.dp[i][3] = 昨天就买了第二次;昨天卖出的第一次的状态,今天才买第二次 + * dp[i][3]=max(dp[i-1][3],dp[i-1][2]-price[i]) + * 5.dp[i][4] = 昨天就卖出了第二次;昨天买入的第二次状态,今天卖了 + * dp[i][4]=max(dp[i-1][4],dp[i-1][3]+price[i]) + * (3)dp初始化:dp[0][0]=0 第一天刚买入dp[0][1]=-price[0];第一天刚买刚卖dp[0][2]=0;第一天刚买刚卖又买dp[0][3]=-price[0];第一天刚买刚卖又买又卖dp[0][4]=0 + * (4)dp遍历顺序:从前往后 + * (5)dp举例推导:以输入[1,2,3,4,5]为例 + * [0 1 2 3 4] + * i=0 0 -1 0 -1 0 + * i=1 0 -1 1 -1 1 + * i=2 0 -1 2 -1 2 + * i=3 0 -1 3 -1 3 + * i=4 0 -1 4 -1 4 + * 速度击败43.68%,内存击败91.87% 20ms + * @param prices + * @return + */ + public int maxProfit(int[] prices) { + + int[][] dp = new int[prices.length][5]; + dp[0][0]=0; + dp[0][1]=-prices[0]; + dp[0][2]=0; + dp[0][3]=-prices[0]; + dp[0][4]=0; + 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][2],dp[i][1]+prices[i]); + dp[i][3]=Math.max(dp[i-1][3],dp[i-1][2]-prices[i]); + dp[i][4]=Math.max(dp[i-1][4],dp[i-1][3]+prices[i]); + } + return dp[prices.length-1][4]; + + } + + + /** + * 滚动数组优化 + * 速度击败86.14%,内存击败55.57% 2ms + * @param prices + * @return + */ + public int maxProfit1(int[] prices) { + int dp0=0; + int dp1=-prices[0]; + int dp2=0; + int dp3=-prices[0]; + int dp4=0; + for (int i = 1; i < prices.length; i++) { + dp1=Math.max(dp1,-prices[i]); + dp2=Math.max(dp2,dp1+prices[i]); + dp3=Math.max(dp3,dp2-prices[i]); + dp4=Math.max(dp4,dp3+prices[i]); + } + return dp4; + + } + + + /** + * 评论区两次遍历dp法:从高到低一次,从低到高一次。两者相加表示两次交易的最大利润总和,取最大值即可 + * 速度击败62.14%,内存击败98.29% + * 十分的精妙,本质上就是把两次买卖分成了两次T21的maxProfit + * 但无法推广到只允许k次交易 + * @param prices + * @return + */ + public int maxProfit2(int[] prices) { + int max = 0; + int minVal = prices[0], maxVal = prices[prices.length-1]; + int[] dp = new int[prices.length];//从低到高,dp[i]表示第i天以及之前的区间所获得的最大利润 + int[] dp2 = new int[prices.length];//从高到低,dp2[i]表示第i天开始直到最后一天期间所获得的最大利润 + dp[0] = -prices[0]; + //这个循环本质上是T21的maxProfit,即寻找从前往后的一次买卖获得最大值 + for(int i=1;i=0;--i){ + dp2[i] = Math.max(dp2[i+1], maxVal-prices[i]); + maxVal = Math.max(maxVal, prices[i]); + } + //把第i天之前的最大值和第i天之后的最大值相加,找到最大值 + for(int i=1;i<=prices.length-1;++i){ + // System.out.println(dp[i-1]+","+dp2[i]); + max = Math.max(dp[i-1]+dp2[i], max); + } + //只买卖一次的,和买卖两次的谁大 + return Math.max(dp[prices.length-1], max); + + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/dynamic/T24_MaxProfit.java b/Leecode/src/main/java/com/markilue/leecode/dynamic/T24_MaxProfit.java new file mode 100644 index 0000000..01191cf --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/dynamic/T24_MaxProfit.java @@ -0,0 +1,82 @@ +package com.markilue.leecode.dynamic; + +import org.junit.Test; + +/** + * @BelongsProject: Leecode + * @BelongsPackage: com.markilue.leecode.dynamic + * @Author: dingjiawen + * @CreateTime: 2022-12-13 12:00 + * @Description: + * TODO 力扣188题 买卖股票的最佳时期IV: + * 给定一个整数数组 prices ,它的第 i 个元素 prices[i] 是一支给定的股票在第 i 天的价格。 + * 设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。 + * 注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。 + * @Version: 1.0 + */ +public class T24_MaxProfit { + + @Test + public void test(){ + int k = 2; + int[] prices = {3, 2, 6, 5, 0, 3}; + System.out.println(maxProfit1(k,prices)); + } + + /** + * 思路:这题与T23十分的类似:只是将2次交易推广到k次交易,事实上T23的状态转移方程也具有推广性 + * TODO 动归五部曲:事实上k次交易可以推广到有2*k+1个状态(无操作,第一次买,第一次卖,...,第k次买,第k次买) + * (1)dp定义:dp[i][0]表示无操作;dp[i][2*k+1]表示第k次买,dp[i][2*(k+1)]表示第k次卖 + * (2)dp状态转移方程: + * 1.dp[i][2*k+1]=max(dp[i-1][2*k+1],dp[2*k]-prices[i]) + * 1.dp[i][2*(k+1)]=max(dp[i-1][2*(k+1)],dp[2*k+1]+prices[i]) + * (3)dp初始化: dp[0][i%2==0]=0;dp[0][i%2==1]=-prices[i] + * (4)dp遍历顺序:从前往后 + * (5)dp举例推导: + * 速度击败99.9%,内部击败36.8% 1ms + * @param k + * @param prices + * @return + */ + public int maxProfit(int k, int[] prices) { + + int[][] dp = new int[prices.length][2 * k + 1]; + for (int i = 0; i < 2 * k + 1; i++) { + dp[0][i] = i % 2 == 0 ? 0 : -prices[0]; + } + + for (int i = 1; i < prices.length; i++) { + for (int j = 0; j < k; j++) { + dp[i][2*j+1]=Math.max(dp[i-1][2*j+1],dp[i-1][2*j]-prices[i]); + dp[i][2*(j+1)]=Math.max(dp[i-1][2*(j+1)],dp[i-1][2*j+1]+prices[i]); + } + } + return dp[prices.length-1][2*k]; + + } + + + /** + * 滚动数组简化 + * 速度击败58.3%,内存击败78.2% 1ms + * @param k + * @param prices + * @return + */ + public int maxProfit1(int k, int[] prices) { + + int[] dp = new int[2 * k + 1]; + for (int i = 0; i < 2 * k + 1; i++) { + dp[i] = i % 2 == 0 ? 0 : -prices[0]; + } + + for (int i = 1; i < prices.length; i++) { + for (int j = 0; j < k; j++) { + dp[2*j+1]=Math.max(dp[2*j+1],dp[2*j]-prices[i]); + dp[2*(j+1)]=Math.max(dp[2*(j+1)],dp[2*j+1]+prices[i]); + } + } + return dp[2*k]; + + } +}