diff --git a/src/main/java/dynamic_programming/MaxProfit.java b/src/main/java/dynamic_programming/MaxProfit.java index 7a08dbf..7564c04 100644 --- a/src/main/java/dynamic_programming/MaxProfit.java +++ b/src/main/java/dynamic_programming/MaxProfit.java @@ -14,6 +14,7 @@ package dynamic_programming; * 链接:https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/ */ //需要琢磨 +//二刷会做 public class MaxProfit { public int maxProfit(int[] prices) { int n = prices.length; diff --git a/src/main/java/dynamic_programming/MaxProfit2.java b/src/main/java/dynamic_programming/MaxProfit2.java index d6a8785..72d05f3 100644 --- a/src/main/java/dynamic_programming/MaxProfit2.java +++ b/src/main/java/dynamic_programming/MaxProfit2.java @@ -5,17 +5,18 @@ package dynamic_programming; * 描述:给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。 * 在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。 * 返回 你能获得的 最大 利润 。 - *

+ * 示例 1: * 输入:prices = [7,1,5,3,6,4] * 输出:7 * 解释:在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4。 * 随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6 - 3 = 3。 * 最大总利润为 4 + 3 = 7 。 - *

+ * 链接:https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/ */ //不会dp做法 +//二刷会做 public class MaxProfit2 { // 由于允许多次交易,「今天买入」的收益要在「昨天不持有」的基础上减去今天的价格;「今天卖出」的收益要在「昨天持有」的基础上加上今天的价格。 // dp[i][0]:第i天持有股票时的最大收益 diff --git a/src/main/java/dynamic_programming/MaxProfit3.java b/src/main/java/dynamic_programming/MaxProfit3.java index 29fb937..65d0f2c 100644 --- a/src/main/java/dynamic_programming/MaxProfit3.java +++ b/src/main/java/dynamic_programming/MaxProfit3.java @@ -16,6 +16,7 @@ package dynamic_programming; * 链接:https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iii/ */ //不会 +//二刷不会 public class MaxProfit3 { /** * 一天一共就有五个状态, diff --git a/src/main/java/greedy/MaxProfit.java b/src/main/java/greedy/MaxProfit.java index 078f07d..fbea2e1 100644 --- a/src/main/java/greedy/MaxProfit.java +++ b/src/main/java/greedy/MaxProfit.java @@ -14,8 +14,20 @@ package greedy; * 链接:https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/ */ - //第一次做没想出来,每次都假设是今天卖出,然后求今天之前的历史最低点。 +//第一次做没想出来,每次都假设是今天卖出,然后求今天之前的历史最低点。 +//二刷会做 public class MaxProfit { + public int maxProfit2(int[] prices) { + int maxprofit=0; + int minprice=prices[0]; + for (int i = 1; i < prices.length; i++) { + if(prices[i]>minprice)maxprofit=Math.max(maxprofit,prices[i]-minprice); + else minprice=prices[i]; + } + return maxprofit; + } + + public int maxProfit(int prices[]) { int minprice = Integer.MAX_VALUE; int maxprofit = 0; diff --git a/src/main/java/greedy/MaxProfit2.java b/src/main/java/greedy/MaxProfit2.java index fdaf883..3aaed55 100644 --- a/src/main/java/greedy/MaxProfit2.java +++ b/src/main/java/greedy/MaxProfit2.java @@ -14,8 +14,15 @@ package greedy; * 链接:https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/ */ - +//二刷会做 public class MaxProfit2 { + public int maxProfit2(int[] prices) { + int maxProfit=0; + for (int i = 1; i < prices.length; i++) { + maxProfit+=Math.max(prices[i]-prices[i-1],0); + } + return maxProfit; + } //考虑复杂了! public int maxProfit1(int[] prices) { int n = prices.length, profit = 0; diff --git a/src/main/java/greedy/MaxProfit3.java b/src/main/java/greedy/MaxProfit3.java index 40e449e..4c1f19a 100644 --- a/src/main/java/greedy/MaxProfit3.java +++ b/src/main/java/greedy/MaxProfit3.java @@ -16,12 +16,13 @@ package greedy; * 链接:https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iii/ */ //不会 困难题 - +//二刷不会 public class MaxProfit3 { /** * 举个小例子 prices = [1, 5, 2, 6](共 4 天): - * - * 左半段你能算出 left = [0, 4, 4, 5]。 + * left[i] 表示在第 0 天到第 i 天范围内,最多做一笔交易能获得的最优利润 + * 左半段:left = [0, 4, 4, 5]。 + * right[i] 表示「从第 i 天到最后一天」这一段区间内,最多做一笔交易能拿到的最大利润 * 右半段: * 以 i=2(价格 2)为起点:未来最高是 6,利润 = 6−2 = 4。 * 以 i=1(价格 5)为起点:未来最高同样 6,利润 = 6−5 = 1。 @@ -33,33 +34,30 @@ public class MaxProfit3 { */ public int maxProfit(int[] prices) { int n = prices.length; - if (n < 2) return 0; // (1) + if (n < 2) return 0; // 1. left[i]: 0…i 天内最多一笔交易的最优收益 int[] left = new int[n]; - int minPrice = prices[0]; // (2) + int minPrice = prices[0]; for (int i = 1; i < n; i++) { - minPrice = Math.min(minPrice, prices[i]); // (3) 更新最低买入价 - left[i] = Math.max(left[i - 1], // (4) 选「不做新交易」或「今天卖出」 - prices[i] - minPrice); + minPrice = Math.min(minPrice, prices[i]); + left[i] = Math.max(left[i - 1], prices[i] - minPrice); } // 2. right[i]: i…n-1 天内最多一笔交易的最优收益 - int[] right = new int[n + 1]; // (5) 右边界额外留 1,用于 i=n-1 时访问 - int maxPrice = prices[n - 1]; // (6) - for (int i = n - 1; i >= 0; i--) { - maxPrice = Math.max(maxPrice, prices[i]); // (7) 更新最高卖出价 - right[i] = Math.max(right[i + 1], // (8) 选「不做新交易」或「今天买入后再卖出」 - maxPrice - prices[i]); + int[] right = new int[n]; + right[n - 1] = 0; // 只有最后一天,无交易可能 + int maxPrice = prices[n - 1]; + for (int i = n - 2; i >= 0; i--) { + maxPrice = Math.max(maxPrice, prices[i]); + right[i] = Math.max(right[i + 1], maxPrice - prices[i]); } - // 3. 枚举切分点:第一笔结束在 i,第二笔从 i+1 开始 - int ans = 0; - for (int i = -1; i < n; i++) { // (9) - int leftProfit = (i >= 0) ? left[i] : 0; // (10) - int rightProfit = (i + 1 < n) ? right[i + 1] : 0; // (11) - ans = Math.max(ans, leftProfit + rightProfit); // (12) + // 3. 合并两段:写法 B(更简洁) + int ans = right[0]; // 只做一笔(当作第二笔) + for (int i = 0; i < n - 1; i++) { + ans = Math.max(ans, left[i] + right[i + 1]); } - return ans; // (13) + return ans; } } diff --git a/src/main/java/实现类功能/MedianFinder.java b/src/main/java/实现类功能/MedianFinder.java new file mode 100644 index 0000000..92fa0c0 --- /dev/null +++ b/src/main/java/实现类功能/MedianFinder.java @@ -0,0 +1,96 @@ +package 实现类功能; + +import java.util.Collections; +import java.util.PriorityQueue; + +/** + * 题目: 295. 数据流的中位数 (partitionMedianFinder) + * 描述:中位数是有序整数列表中的中间值。如果列表的大小是偶数,则没有中间值,中位数是两个中间值的平均值。 + * 例如 arr = [2,3,4] 的中位数是 3 。 + * 例如 arr = [2,3] 的中位数是 (2 + 3) / 2 = 2.5 。 + * 实现 MedianFinder 类: + * MedianFinder() 初始化 MedianFinder 对象。 + + * void addNum(int num) 将数据流中的整数 num 添加到数据结构中。 + + * double findMedian() 返回到目前为止所有元素的中位数。与实际答案相差 10-5 以内的答案将被接受。 + + 示例 1: + 输入 + ["MedianFinder", "addNum", "addNum", "findMedian", "addNum", "findMedian"] + [[], [1], [2], [], [3], []] + 输出 + [null, null, null, 1.5, null, 2.0] + + 解释 + MedianFinder medianFinder = new MedianFinder(); + medianFinder.addNum(1); // arr = [1] + medianFinder.addNum(2); // arr = [1, 2] + medianFinder.findMedian(); // 返回 1.5 ((1 + 2) / 2) + medianFinder.addNum(3); // arr[1, 2, 3] + medianFinder.findMedian(); // return 2.0 + + * 链接:https://leetcode.cn/problems/palindrome-partitioning/ + */ +//困难题,二刷不会 + +/** + * 使用一个小顶堆 A(存储较大的一半)和一个大顶堆 B(存储较小的一半),并在每次插入后保持以下不变式: + * A 的大小 = B 的大小,或 A 的大小 = B 的大小 + 1 + * A 中始终保存「较大的一半」元素,长度为 N/2(N 为偶数)或 (N+1)/2(N 为奇数)。 + * B 中始终保存「较小的一半」元素,长度为 N/2(N 为偶数)或 (N–1)/2(N 为奇数)。 + */ +public class MedianFinder { + // 小顶堆 A:保存较大的一半元素 + private PriorityQueue minHeap; + // 大顶堆 B:保存较小的一半元素 + private PriorityQueue maxHeap; + + /** 初始化两个堆 */ + public MedianFinder() { + // 默认 Java 的 PriorityQueue 是小顶堆 + minHeap = new PriorityQueue<>(); + // 传入逆序比较器,构造大顶堆 + maxHeap = new PriorityQueue<>(Collections.reverseOrder()); + } + + /** + * 添加一个数到数据结构中: + * 1. 先根据与 A 堆顶的大小关系,决定插入 A 还是 B + * 2. 然后调整两堆大小,使得 size(A)==size(B) 或 size(A)==size(B)+1 + * O(log n) + */ + public void addNum(int num) { + // 如果 A 为空,或 num >= A 的最小值,就放入 A;否则放入 B + if (minHeap.isEmpty() || num >= minHeap.peek()) { + minHeap.offer(num); + } else { + maxHeap.offer(num); + } + + // 平衡:如果 A 太大,搬一个到 B + if (minHeap.size() > maxHeap.size() + 1) { + maxHeap.offer(minHeap.poll()); + } + // 如果 B 太大,搬一个到 A + else if (maxHeap.size() > minHeap.size()) { + minHeap.offer(maxHeap.poll()); + } + } + + /** + * 查找当前所有数的中位数: + * - 若总数为奇数:A 比 B 多 1 个元素,中位数就是 A 堆顶 + * - 若总数为偶数:A、B 等大,中位数是两堆顶元素的平均值 + * O(1) + */ + public double findMedian() { + if (minHeap.size() > maxHeap.size()) { + // 奇数个元素,中位数在 A + return minHeap.peek(); + } else { + // 偶数个元素,取平均 + return (minHeap.peek() + maxHeap.peek()) / 2.0; + } + } +}