From 620e720dba987a6246cd6b1eb323b1e19739915e Mon Sep 17 00:00:00 2001 From: zhangsan <646228430@qq.com> Date: Tue, 27 May 2025 09:57:47 +0800 Subject: [PATCH] =?UTF-8?q?5.27=20=E4=B9=B0=E5=8D=96=E8=82=A1=E7=A5=A8=20+?= =?UTF-8?q?=20=E8=B4=AA=E5=BF=83=E5=8C=BA=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/dynamic_programming/MaxProfit4.java | 2 +- .../java/dynamic_programming/MaxProfit5.java | 60 ++++++++++++++++ .../java/dynamic_programming/MaxProfit6.java | 34 +++++++++ .../java/range/EraseOverlapIntervals.java | 72 +++++++++++++++++++ src/main/java/range/FindMinArrowShots.java | 35 +++++++-- .../dynamic_programming/MaxProfit6Test.java | 17 +++++ 6 files changed, 214 insertions(+), 6 deletions(-) create mode 100644 src/main/java/dynamic_programming/MaxProfit5.java create mode 100644 src/main/java/dynamic_programming/MaxProfit6.java create mode 100644 src/main/java/range/EraseOverlapIntervals.java create mode 100644 src/test/java/dynamic_programming/MaxProfit6Test.java diff --git a/src/main/java/dynamic_programming/MaxProfit4.java b/src/main/java/dynamic_programming/MaxProfit4.java index ad117ff..1d40035 100644 --- a/src/main/java/dynamic_programming/MaxProfit4.java +++ b/src/main/java/dynamic_programming/MaxProfit4.java @@ -10,7 +10,7 @@ package dynamic_programming; 输出:2 解释:在第 1 天 (股票价格 = 2) 的时候买入,在第 2 天 (股票价格 = 4) 的时候卖出,这笔交易所能获得利润 = 4-2 = 2 。 - * 链接:https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iii/ + * 链接:https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iv/ */ //不会 public class MaxProfit4 { diff --git a/src/main/java/dynamic_programming/MaxProfit5.java b/src/main/java/dynamic_programming/MaxProfit5.java new file mode 100644 index 0000000..7d75af0 --- /dev/null +++ b/src/main/java/dynamic_programming/MaxProfit5.java @@ -0,0 +1,60 @@ +package dynamic_programming; +/** + * 题目: 309. 买卖股票的最佳时机含冷冻期 (maxProfit) + * 描述:给定一个整数数组prices,其中第 prices[i] 表示第 i 天的股票价格 。​ + * 设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票): + * 卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。 + * 注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。 + + * 示例 1: + 输入: prices = [1,2,3,0,2] + 输出: 3 + 解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出] + + * 链接:https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-cooldown/ + */ +//不会 +public class MaxProfit5 { + public int maxProfit(int[] prices) { + int n = prices.length; + if (n == 0) return 0; + + // dp[i][0]: 持有股票 + // dp[i][1]: 不持有,且不在冷冻期(可买入/休息) + // dp[i][2]: 今天卖出(刚卖出) + // dp[i][3]: 今天冷冻期(昨天卖出) + int[][] dp = new int[n][4]; + + // 第 0 天的初始状态 + dp[0][0] = -prices[0]; // 买入 + dp[0][1] = 0; // 空仓休息 + dp[0][2] = 0; // 不可能刚卖出 + dp[0][3] = 0; // 不可能处于冷冻期 + + // 从第 1 天开始状态转移 + for (int i = 1; i < n; i++) { + int p = prices[i]; + + // 1. 持有股票:要么延续持有,要么从空仓/冷冻期买入 + dp[i][0] = Math.max( + dp[i-1][0], + Math.max(dp[i-1][1] - p, dp[i-1][3] - p) + ); + + // 2. 不持有且不在冷冻期:要么昨儿就休息,要么昨儿冷冻期结束 + dp[i][1] = Math.max(dp[i-1][1], dp[i-1][3]); + + // 3. 今天卖出:一定是从持有状态卖出 + dp[i][2] = dp[i-1][0] + p; + + // 4. 今天冷冻期:只能由昨天卖出进入 + dp[i][3] = dp[i-1][2]; + } + + // 最后一天不持有股票的三种状态取最大 + return Math.max( + Math.max(dp[n-1][1], dp[n-1][2]), + dp[n-1][3] + ); + } +} diff --git a/src/main/java/dynamic_programming/MaxProfit6.java b/src/main/java/dynamic_programming/MaxProfit6.java new file mode 100644 index 0000000..0dfd3c3 --- /dev/null +++ b/src/main/java/dynamic_programming/MaxProfit6.java @@ -0,0 +1,34 @@ +package dynamic_programming; +/** + * 题目: 714. 买卖股票的最佳时机含手续费 (maxProfit) + * 描述:给定一个整数数组 prices,其中 prices[i]表示第 i 天的股票价格 ;整数 fee 代表了交易股票的手续费用。 + * + * 你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。 + * + * 返回获得利润的最大值。 + * + * 注意:这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。 + + * 示例 1: + 输入:prices = [1, 3, 2, 8, 4, 9], fee = 2 + 输出:8 + 解释:能够达到的最大利润: + 在此处买入 prices[0] = 1 + 在此处卖出 prices[3] = 8 + 在此处买入 prices[4] = 4 + 在此处卖出 prices[5] = 9 + 总利润: ((8 - 1) - 2) + ((9 - 4) - 2) = 8 + + * 链接:https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/ + */ +public class MaxProfit6 { + public int maxProfit(int[] prices, int fee) { + int[][]dp=new int[prices.length][2]; + dp[0][0]=-prices[0]-fee; + for (int i = 1; i < prices.length; i++) { + dp[i][0]=Math.max(dp[i-1][0],dp[i-1][1]-prices[i]-fee); + dp[i][1]=Math.max(dp[i-1][1],dp[i-1][0]+prices[i]); + } + return dp[prices.length-1][1]; + } +} diff --git a/src/main/java/range/EraseOverlapIntervals.java b/src/main/java/range/EraseOverlapIntervals.java new file mode 100644 index 0000000..3da8cf7 --- /dev/null +++ b/src/main/java/range/EraseOverlapIntervals.java @@ -0,0 +1,72 @@ +package range; + +import java.util.Arrays; + +/** + * 题目: 435. 无重叠区间 (eraseOverlapIntervals) + * 描述:给定一个区间的集合 intervals ,其中 intervals[i] = [starti, endi] 。返回 需要移除区间的最小数量,使剩余区间互不重叠 。 + * + * 注意 只在一点上接触的区间是 不重叠的。例如 [1, 2] 和 [2, 3] 是不重叠的。 + + * 示例 1: + 输入: intervals = [[1,2],[2,3],[3,4],[1,3]] + 输出: 1 + 解释: 移除 [1,3] 后,剩下的区间没有重叠。 + + * 链接:https://leetcode.cn/problems/non-overlapping-intervals/ + */ +//不会 +public class EraseOverlapIntervals { + /** + * 1.按结束时间排序 + * 对所有区间按它们的右端点(end)从小到大排序。 + * 这样,我们每次选取能最早「结束」的区间,就能给后面留下最大的空间,容纳更多不重叠的区间。 + * + * 2.一次线性扫描,维护「当前结束」 + * 用变量 end 记录上一次加入的(未移除的)区间的结束位置,初始化为排序后第一个区间的结束。 + * 用 count 记录当前保留下来的不重叠区间数量,初始化为 1(第一个区间肯定留下)。 + * 从第二个区间开始遍历: + * 如果当前区间的起点 start ≥ end,说明它与上一次保留的区间不重叠,可以「保留」: + * count++,并更新 end = 当前区间的 end。 + * 否则,它与上一次保留的区间发生重叠,必须移除其中一个。 + * 为了让后续更容易接上,我们应当移除「结束得更晚」的那个区间,也就是保留结束更早的那个(即当前排序中的那个已经在保留的区间),此时不更新 end,相当于跳过(移除)当前区间。 + * + * 3.计算需移除的数量 + * 最终保留下来的区间数为 count,总区间数为 n,所以最少要移除 n - count 个。 + * @param intervals + * @return + */ + public int eraseOverlapIntervals(int[][] intervals) { + //0,3 2,5 4,7 + int n = intervals.length; + if (n == 0) { + // 没有区间,当然不需要移除 + return 0; + } + + // 1. 按照区间的结束时间(end)从小到大排序 + Arrays.sort(intervals, (a, b) -> Integer.compare(a[1], b[1])); + + // 2. 初始化:保留第一个区间 + int count = 1; // 当前保留的不重叠区间数量 + int end = intervals[0][1]; // 最近一次保留区间的结束时间 + + // 3. 遍历其余区间 + for (int i = 1; i < n; i++) { + int startI = intervals[i][0]; + int endI = intervals[i][1]; + + if (startI >= end) { + // 不重叠:可以保留 + count++; + end = endI; // 更新「最近一次保留区间」的结束时间 + } + // 否则:重叠。移除「结束更晚」的那个区间。 + // 因为我们是按 end 升序遍历的,当前 intervals[i] 的 end ≥ end, + // 所以移除它即可;无需更新 end。 + } + + // 4. 最少移除数 = 总数 - 保留下来的数量 + return n - count; + } +} diff --git a/src/main/java/range/FindMinArrowShots.java b/src/main/java/range/FindMinArrowShots.java index c48f66f..022cba0 100644 --- a/src/main/java/range/FindMinArrowShots.java +++ b/src/main/java/range/FindMinArrowShots.java @@ -19,16 +19,41 @@ import java.util.HashMap; * 链接:https://leetcode.cn/problems/minimum-number-of-arrows-to-burst-balloons/ */ public class FindMinArrowShots { + /** + * 一种经典做法是:先按照每个气球的 右端点 从小到大排序; + * 然后从最左边的气球开始,选它的右端点作为射箭位置,这样能保证这支箭能戳破尽可能多的 “覆盖” 当前右端点的气球; + * 跳过所有已经被这支箭打到的气球,再对下一个未被覆盖的气球重复相同操作。 + * @param points + * @return + */ public int findMinArrowShots(int[][] points) { + int n = points.length; + if (n == 0) { + // 没有气球,当然不用射箭 + return 0; + } + + // 1. 按照区间的右端点从小到大排序 + // 这样每次选当前最小的右端点作为射箭位置, + // 能最大程度地覆盖后续尽可能多的气球 Arrays.sort(points, (a, b) -> Integer.compare(a[1], b[1])); - int index=0,cnt=0; - while(index=points[index][0]) - index++; + int cnt = 0; // 箭的数量 + int index = 0; // 当前未覆盖的气球下标 + + // 2. 遍历所有气球,只要还有未戳破的,就再射一支箭 + while (index < n) { + // 这一支箭射在 points[index][1] 处(当前未处理区间的最小右端) + int shootPos = points[index][1]; cnt++; + + // 3. 跳过所有能够被这支箭戳破的气球 + // 条件:它们的左端 ≤ shootPos + while (index < n && points[index][0] <= shootPos) { + index++; + } } + return cnt; } } diff --git a/src/test/java/dynamic_programming/MaxProfit6Test.java b/src/test/java/dynamic_programming/MaxProfit6Test.java new file mode 100644 index 0000000..58fb065 --- /dev/null +++ b/src/test/java/dynamic_programming/MaxProfit6Test.java @@ -0,0 +1,17 @@ +package dynamic_programming; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class MaxProfit6Test { + + @Test + public void maxProfit() { + int[]prices = {1,3,7,5,10,3}; + int fee = 3; + MaxProfit6 maxProfit6 = new MaxProfit6(); + int res=maxProfit6.maxProfit(prices,fee); + System.out.println(res); + } +} \ No newline at end of file