每日算法-250421
每日算法学习 2025-04-21
记录今天学习的几道 LeetCode 算法题,主要涉及贪心策略。
2126. 摧毁小行星
题目描述:
思路
贪心
解题过程
这道题的目标是尽可能多地摧毁小行星。我们可以观察到,摧毁小行星可以增加我们的质量,而更大的质量能让我们摧毁更大的小行星。这是一个正向循环。
因此,一个自然的贪心策略是:优先摧毁质量最小的小行星。这样可以以最小的代价来获得增长,从而更有可能摧毁后续更大的小行星。
具体步骤如下:
- 对小行星数组
asteroids
进行升序排序。 - 初始化当前质量
MASS
(使用long
类型防止溢出)。 - 遍历排序后的小行星数组:
- 如果当前质量
MASS
小于当前遇到的小行星质量asteroid
,则无法摧毁,直接返回false
。 - 如果可以摧毁,将小行星的质量加到当前质量
MASS
上 (MASS += asteroid
)。
- 如果当前质量
- 如果成功遍历完所有小行星,说明都可以摧毁,返回
true
。
注意: 累加质量时,总质量可能会超过 int
的最大值,因此需要使用 long
类型来存储当前质量。
复杂度
- 时间复杂度: O ( N log N ) O(N \log N) O(NlogN),主要来自对小行星数组的排序。
- 空间复杂度: O ( 1 ) O(1) O(1) 或 O ( log N ) O(\log N) O(logN),取决于排序算法使用的额外空间(通常认为原地排序为 O ( 1 ) O(1) O(1) 辅助空间)。
Code
class Solution {public boolean asteroidsDestroyed(int mass, int[] asteroids) {Arrays.sort(asteroids);long MASS = mass;for (int asteroid : asteroids) {if (MASS < asteroid) {return false;}MASS += asteroid;}return true;}
}
2587. 重排数组以得到最大前缀分数
题目描述:
思路
贪心
解题过程
题目要求我们通过重排数组 nums
来最大化其前缀和为正数的数量(即“分数”)。
考虑前缀和的计算方式 prefix[i] = nums[0] + nums[1] + ... + nums[i]
。为了让尽可能多的前缀和保持正数,我们应该:
- 优先累加正数:正数总会增加前缀和,有助于保持其为正。
- 将较大的数放在前面:这样可以使初始的前缀和尽可能大,为后面可能出现的负数提供缓冲。
- 将负数(尤其是绝对值大的负数)放在后面:负数会减小前缀和,应尽量推迟它们的影响。
具体步骤:
- 对数组
nums
进行排序(升序或降序皆可,后续处理方式不同)。 - 如果按升序排序,则从数组末尾(最大元素)开始向前遍历;如果按降序排序,则从头开始遍历。
- 维护一个当前前缀和
sum
(使用long
类型防止溢出)。 - 遍历过程中,累加当前元素到
sum
。 - 如果
sum > 0
,则计数器ret
加 1。 - 如果
sum <= 0
,说明从此点开始,后续的前缀和(如果有的话,因为添加了更小的或负数)也不会大于 0 了(因为数组已排序),可以直接停止遍历并返回当前的ret
。 - 遍历完成后,返回
ret
。
特殊情况:如果排序后最大的元素(即 nums[nums.length - 1]
如果是升序排序)都小于等于 0,那么不可能有任何正的前缀和,直接返回 0。
注意: 前缀和 sum
可能会溢出 int
,需要使用 long
类型。
复杂度
- 时间复杂度: O ( N log N ) O(N \log N) O(NlogN),主要来自排序。
- 空间复杂度: O ( 1 ) O(1) O(1) 或 O ( log N ) O(\log N) O(logN),取决于排序算法使用的额外空间。
Code
class Solution {public int maxScore(int[] nums) {// 1. 升序排序Arrays.sort(nums);// 特殊情况:最大值都 <= 0,不可能有正前缀和if (nums[nums.length - 1] <= 0) {return 0;}// 2. 使用 long 防止溢出long sum = 0;int score = 0;// 3. 从大到小遍历(因为是升序排序,所以从后往前)for (int i = nums.length - 1; i >= 0; i--) {// 4. 累加当前元素sum += nums[i];// 5. 如果当前前缀和 <= 0,后续不可能再 > 0if (sum <= 0) {break;}// 6. 否则,分数 +1score++;}return score;}
}
976. 三角形的最大周长
题目描述:
思路
贪心
解题过程
我们要找能组成三角形的三条边的最大周长。
根据三角形的性质,任意两边之和必须大于第三边。即对于边长 a, b, c
,需要满足 a + b > c
, a + c > b
, b + c > a
。
为了使周长 a + b + c
最大,我们应该尽可能选择较长的边。
一个贪心策略是:
- 对数组
nums
进行升序排序。 - 从最长的边开始尝试。考虑数组中最大的三个数
nums[i]
,nums[i-1]
,nums[i-2]
(其中i
是数组最后一个元素的索引)。 - 检查这三条边是否能构成三角形。由于我们已经排序 (
nums[i-2] <= nums[i-1] <= nums[i]
),我们只需要检查最短的两边之和是否大于最长边即可,即nums[i-2] + nums[i-1] > nums[i]
。因为其他两个条件 (nums[i-2] + nums[i] > nums[i-1]
和nums[i-1] + nums[i] > nums[i-2]
) 必然成立。 - 如果
nums[i-2] + nums[i-1] > nums[i]
成立,那么这三条边构成了当前能找到的最大周长的三角形(因为我们是从最长的边开始尝试的),直接返回它们的和nums[i] + nums[i-1] + nums[i-2]
。 - 如果不满足条件,说明以
nums[i]
作为最长边无法构成三角形,那么需要放弃nums[i]
,尝试下一组可能的最大边组合,即考虑nums[i-1]
,nums[i-2]
,nums[i-3]
,继续这个过程。也就是将i
减 1,重复步骤 3。 - 从数组末尾向前遍历,直到找到第一个满足条件的三个连续元素,或者遍历完所有可能的三个连续组合(即
i
减小到 2)。 - 如果遍历结束都没有找到能组成三角形的三条边,说明无法构成任何三角形,返回 0。
复杂度
- 时间复杂度: O ( N log N ) O(N \log N) O(NlogN),主要来自排序。
- 空间复杂度: O ( 1 ) O(1) O(1) 或 O ( log N ) O(\log N) O(logN),取决于排序算法使用的额外空间。
Code
class Solution {public int largestPerimeter(int[] nums) {// 1. 升序排序Arrays.sort(nums);// 2. 从数组末尾向前遍历,寻找满足条件的三个连续元素// i 指向潜在的最长边for (int i = nums.length - 1; i >= 2; i--) {// 3. 检查最短两边之和是否大于最长边if (nums[i - 2] + nums[i - 1] > nums[i]) {// 4. 如果满足,这就是能组成的最大周长三角形return nums[i] + nums[i - 1] + nums[i - 2];}// 5. 如果不满足,继续向前尝试更小的边组合}// 6. 遍历完成仍未找到,返回 0return 0;}
}
1170. 比较字符串最小字母出现频次 (复习)
题目描述:
这是第二次做这道题了,这次完全理清了思路,非常顺畅地写出来了。详细题解可以参考之前的笔记 每日算法-250408。
Code
class Solution {public int[] numSmallerByFrequency(String[] queries, String[] words) {int qLen = queries.length, wLen = words.length;// 1. 转换数组int[] Queries = new int[qLen];for (int i = 0; i < qLen; i++) {Queries[i] = f(queries[i]);}int[] Words = new int[wLen];for (int i = 0; i < wLen; i++) {Words[i] = f(words[i]);}Arrays.sort(Words);// 2. 二分查找for (int i = 0; i < qLen; i++) {Queries[i] = wLen - searchIndex(Words, Queries[i]);}return Queries;}private int searchIndex(int[] arr, int t) {int left = 0, right = arr.length - 1;while (left <= right) {int mid = left + (right - left) / 2;if (arr[mid] <= t) {left = mid + 1;} else {right = mid - 1;}}return left;}private int f(String s) {char minChar = 'z';int count = 0;for (char ch : s.toCharArray()) {if (ch < minChar) {minChar = ch;count = 1;} else if (ch == minChar) {count++;}}return count;}
}