From ed12ee8d52f4998998bc41df769bc19ffbdf51ac Mon Sep 17 00:00:00 2001 From: CyC2018 Date: Fri, 30 Oct 2026 20:18:40 +0800 Subject: [PATCH] =?UTF-8?q?Update=20Leetcode=20=E9=A2=98=E8=A7=A3=20-=20?= =?UTF-8?q?=E8=B4=AA=E5=BF=83=E6=80=9D=E6=83=B3.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 更新贪心策略证明与比较器说明,补充身高重建队列、划分字母区间等题目的贪心思路与推导演示,文末新增参考资料。 --- notes/Leetcode 题解 - 贪心思想.md | 56 +++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 18 deletions(-) diff --git a/notes/Leetcode 题解 - 贪心思想.md b/notes/Leetcode 题解 - 贪心思想.md index 2e47bfbe..b407cd06 100644 --- a/notes/Leetcode 题解 - 贪心思想.md +++ b/notes/Leetcode 题解 - 贪心思想.md @@ -12,6 +12,7 @@ * [9. 修改一个数成为非递减数组](#9-修改一个数成为非递减数组) * [10. 子数组最大的和](#10-子数组最大的和) * [11. 分隔字符串使同种字符出现在一起](#11-分隔字符串使同种字符出现在一起) + * [参考资料](#参考资料) @@ -33,9 +34,12 @@ Output: 2 1. 给一个孩子的饼干应当尽量小并且又能满足该孩子,这样大饼干才能拿来给满足度比较大的孩子。 2. 因为满足度最小的孩子最容易得到满足,所以先满足满足度最小的孩子。 -在以上的解法中,只在每次分配饼干时选择一种看起来是当前最优的分配方法,但无法保证这种局部最优的分配方法最后能得到全局最优解。可以用反证法证明该贪心策略能得到全局最优解:假设存在一种比该贪心策略更优的最优策略。如果不存在这种最优策略,表示贪心策略就是最优策略,得到的解也就是全局最优解。 +局部最优的选择不一定得到全局最优,需要证明。可以用交换论证:设某个最优方案给满足度最小的孩子分配了饼干 n,贪心选的是能满足这个孩子的最小饼干 m,m 不大于 n。 -证明:假设在某次选择中,贪心策略选择给当前满足度最小的孩子分配第 m 个饼干,第 m 个饼干为可以满足该孩子的最小饼干。假设存在一种最优策略,可以给该孩子分配第 n 个饼干,并且 m \< n。经过这一轮分配,贪心策略分配后剩下的饼干一定有一个比最优策略来得大。因此在后续的分配中,贪心策略一定能满足更多的孩子。也就是说不存在比贪心策略更优的策略,即贪心策略就是最优策略。 +- 如果 m 没有被最优方案使用,把 n 换成 m,满足的孩子数不变; +- 如果 m 分给了另一个孩子,两人交换饼干,那个孩子拿到的 n 不小于 m,仍然能被满足。 + +交换后的方案仍然最优,并且第一步与贪心相同。对剩下的孩子和饼干重复这个过程,最优方案可以逐步变成贪心方案,因此贪心方案也是最优的。

@@ -104,18 +108,7 @@ public int eraseOverlapIntervals(int[][] intervals) { } ``` -使用 lambda 表达式创建 Comparator 会导致算法运行时间过长,如果注重运行时间,可以修改为普通创建 Comparator 语句: - -```java -Arrays.sort(intervals, new Comparator() { - @Override - public int compare(int[] o1, int[] o2) { - return (o1[1] < o2[1]) ? -1 : ((o1[1] == o2[1]) ? 0 : 1); - } -}); -``` - -实现 compare() 函数时避免使用 `return o1[1] - o2[1];` 这种减法操作,防止溢出。 +需要注意的是,比较器用 Comparator.comparingInt 或 Integer.compare,避免写成 `o1[1] - o2[1]`:例如下一题的坐标可以取到 -231,减去正数就会溢出。 ## 3. 投飞镖刺破气球 @@ -133,7 +126,7 @@ Output: 题目描述:气球在一个水平数轴上摆放,可以重叠,飞镖垂直投向坐标轴,使得路径上的气球都被刺破。求解最小的投飞镖次数使所有气球都被刺破。 -也是计算不重叠的区间个数,不过和 Non-overlapping Intervals 的区别在于,[1, 2] 和 [2, 3] 在本题中算是重叠区间。 +也是计算不重叠的区间个数,不过和[不重叠的区间个数](#2-不重叠的区间个数)的区别在于,[1, 2] 和 [2, 3] 在本题中算是重叠区间,一支飞镖投在 2 上可以同时刺破它们。 ```java public int findMinArrowShots(int[][] points) { @@ -167,11 +160,24 @@ Output: [[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]] ``` -题目描述:一个学生用两个分量 (h, k) 描述,h 表示身高,k 表示排在前面的有 k 个学生的身高比他高或者和他一样高。 +题目描述:一个学生用两个分量 (h, k) 描述,h 表示身高,k 表示排在前面的有 k 个学生的身高比这个学生高或者和这个学生一样高。 为了使插入操作不影响后续的操作,身高较高的学生应该先做插入操作,否则身高较小的学生原先正确插入的第 k 个位置可能会变成第 k+1 个位置。 -身高 h 降序、个数 k 值升序,然后将某个学生插入队列的第 k 个位置中。 +身高 h 降序、个数 k 值升序,然后将某个学生插入队列的第 k 个位置中。身高相同时 k 小的先插:同样高的人也计入 k,k 大的人后插,前面才有足够的人。以输入为例,排序后依次插入: + +```text +排序后:[7,0] [7,1] [6,1] [5,0] [5,2] [4,4] + +插入 [7,0] 到位置 0:[7,0] +插入 [7,1] 到位置 1:[7,0] [7,1] +插入 [6,1] 到位置 1:[7,0] [6,1] [7,1] +插入 [5,0] 到位置 0:[5,0] [7,0] [6,1] [7,1] +插入 [5,2] 到位置 2:[5,0] [7,0] [5,2] [6,1] [7,1] +插入 [4,4] 到位置 4:[5,0] [7,0] [5,2] [6,1] [4,4] [7,1] +``` + +插入 [5,2] 时,队列中的人都不比 5 矮,放在位置 2 就正好有 2 个人在前面;之后插入的人都更矮,不计入它的 k。 ```java public int[][] reconstructQueue(int[][] people) { @@ -248,6 +254,8 @@ Output: True 题目描述:flowerbed 数组中 1 表示已经种下了花朵。花朵之间至少需要一个单位的间隔,求解是否能种下 n 朵花。 +从左到右能种就种。尽早种不会让结果变差:种在这里只挡住右边一个位置,推迟到更右的空位再种,留给后面的位置只会更少。 + ```java public boolean canPlaceFlowers(int[] flowerbed, int n) { int len = flowerbed.length; @@ -278,6 +286,8 @@ s = "abc", t = "ahbgdc" Return true. ``` +对 s 的每个字符,在 t 中上一个匹配位置之后找最早出现的位置,给后面的字符留下的空间最大。 + ```java public boolean isSubsequence(String s, String t) { int index = -1; @@ -305,7 +315,9 @@ Explanation: You could modify the first 4 to 1 to get a non-decreasing array. 题目描述:判断一个数组是否能只修改一个数就成为非递减数组。 -在出现 nums[i] \< nums[i - 1] 时,需要考虑的是应该修改数组的哪个数,使得本次修改能使 i 之前的数组成为非递减数组,并且 **不影响后续的操作** 。优先考虑令 nums[i - 1] = nums[i],因为如果修改 nums[i] = nums[i - 1] 的话,那么 nums[i] 这个数会变大,就有可能比 nums[i + 1] 大,从而影响了后续操作。还有一个比较特别的情况就是 nums[i] \< nums[i - 2],修改 nums[i - 1] = nums[i] 不能使数组成为非递减数组,只能修改 nums[i] = nums[i - 1]。 +在出现 nums[i] \< nums[i - 1] 时,需要考虑的是应该修改数组的哪个数,使得本次修改能使 i 之前的数组成为非递减数组,并且不影响后续的操作。优先考虑令 nums[i - 1] = nums[i],因为如果修改 nums[i] = nums[i - 1] 的话,那么 nums[i] 这个数会变大,就有可能比 nums[i + 1] 大,从而影响了后续操作。 + +还有一个比较特别的情况就是 nums[i] \< nums[i - 2],修改 nums[i - 1] = nums[i] 不能使数组成为非递减数组,只能修改 nums[i] = nums[i - 1]。例如 [3, 4, 2, 5] 中 2 \< 3,把 4 改为 2 得到 [3, 2, 2, 5] 仍然下降,只能把 2 改为 4。 ```java public boolean checkPossibility(int[] nums) { @@ -338,6 +350,8 @@ For example, given the array [-2,1,-3,4,-1,2,1,-5,4], the contiguous subarray [4,-1,2,1] has the largest sum = 6. ``` +preSum 表示以当前元素结尾的子数组的最大和。如果前面的 preSum 不大于 0,接上它不会让和变大,不如从当前元素重新开始。 + ```java public int maxSubArray(int[] nums) { if (nums == null || nums.length == 0) { @@ -368,6 +382,8 @@ This is a partition so that each letter appears in at most one part. A partition like "ababcbacadefegde", "hijhklij" is incorrect, because it splits S into less parts. ``` +先记录每个字符最后出现的位置。段内每出现一个字符,这一段至少要延伸到它最后出现的位置,因此用它更新 lastIndex;扫描到 lastIndex 时段内字符都不再出现,在这里切开就是最短的一段。例如第二段从 d 开始,d 最后出现在 14,段内的 e 最后出现在 15,于是延伸到 15,长度为 7。 + ```java public List partitionLabels(String S) { int[] lastIndexsOfChar = new int[26]; @@ -394,3 +410,7 @@ private int char2Index(char c) { return c - 'a'; } ``` + +## 参考资料 + +- [LeetCode 题库](https://leetcode.com/problemset/)