每日算法-250424
每日算法打卡 (24/04/25) - LeetCode 2971 & 1647
记录一下今天解决的两道 LeetCode 题目
2971. 找到最大周长的多边形
题目
思路
贪心
一个基本的多边形构成条件是:最长边必须小于其他所有边的长度之和。
为了找到周长最大的多边形,我们应该尽可能地包含更多的边。因此,一个自然的贪心策略是先将所有可能的边长进行排序。
解题过程
- 排序:将边长数组
nums
从小到大排序。这是贪心策略的基础,方便我们从小到大尝试构建多边形。 - 遍历与累加:我们维护一个当前边长之和
sum
。从最短的边开始累加。 - 判断合法性:当我们考虑加入第
i
条边 (长度为nums[i]
) 时,需要检查之前所有边的和sum
(即nums[0] + ... + nums[i-1]
) 是否大于nums[i]
。- 根据多边形构成条件,如果
sum > nums[i]
,那么以nums[i]
作为最长边,加上之前i
条边(总共i+1
条边)可以构成一个合法的多边形。其周长为sum + nums[i]
。因为我们希望周长尽可能大(包含尽可能多的边),所以我们记录下这个合法的周长。 - 如果
sum <= nums[i]
,则无法以nums[i]
作为最长边构成多边形。
- 根据多边形构成条件,如果
- 更新最大周长:我们用一个变量
maxPerimeter
来记录遍历过程中遇到的最大合法周长。只有当sum > nums[i]
时,我们才更新maxPerimeter = sum + nums[i]
。 - 继续累加:无论当前
nums[i]
能否构成合法多边形,我们都将其加入sum
(sum += nums[i]
),以便为后续判断更长的边做准备。 - 初始条件与返回:多边形至少需要3条边。我们从数组的第三个元素(索引
i = 2
)开始检查。如果遍历结束后,maxPerimeter
仍然是初始值(例如 0 或 -1,表示从未找到合法的多边形),则返回 -1。否则,返回maxPerimeter
。
- 代码细节:
sum
在代码中实际上是nums[0] + ... + nums[i]
的前缀和。trueSum
对应我们上面说的maxPerimeter
。index
在代码中记录的是构成最大周长多边形的最长边的索引。检查index < 2
等价于检查是否找到了至少一个包含3条边的合法多边形(因为index
初始为1,只有当i=2
时满足条件,index
才会更新为2)。
复杂度
- 时间复杂度: O ( N log N ) O(N \log N) O(NlogN),主要是排序数组
nums
的时间。遍历数组需要 O ( N ) O(N) O(N)。 - 空间复杂度: O ( log N ) O(\log N) O(logN) 或 O ( N ) O(N) O(N),取决于排序算法使用的栈空间。如果忽略排序的辅助空间(或视为原地排序),则为 O ( 1 ) O(1) O(1)。
Code
class Solution {public long largestPerimeter(int[] nums) {Arrays.sort(nums);long sum = nums[0] + nums[1];long trueSum = 0;int index = 1;for (int i = 2; i < nums.length; i++) {if (sum <= nums[i]) {sum += nums[i];} else {index = i;sum += nums[i];trueSum = sum;}}return index < 2 ? -1 : trueSum;}
}
1647. 字符频次唯一的最小删除次数
题目
思路
贪心
目标是最小化删除次数,使得所有存在字符的频次都是唯一的。
我们可以先统计每个字符的出现频次。为了方便处理冲突(频次相同),一个好的策略是处理排序后的频次。
解题过程
- 统计频次:使用一个数组(大小为26)统计字符串
s
中每个字符 (‘a’ 到 ‘z’) 的出现次数。 - 排序频次:将得到的频次(只考虑大于0的频次,或者处理整个频次数组)进行升序排序。
- 处理冲突(贪心):从后往前(即从最大的频次开始)遍历排序后的频次数组
hash
。- 我们检查当前频次
hash[i]
是否大于等于它前一个的频次hash[i-1]
。 - 如果
hash[i] <= hash[i-1]
,说明出现了频次冲突或者顺序不对(因为已经排序了,所以只会是相等的情况)。我们需要减少hash[i-1]
的值,使其严格小于hash[i]
。 - 贪心选择:为了最小化删除次数,我们将冲突的频次
hash[i-1]
减少到max(0, hash[i] - 1)
。这样既保证了唯一性,又使得减少的量(即删除的字符数)最少。 - 累加删除次数:将原始频次与修改后频次之差累加到总删除次数
count
中。 - 注意:修改后的频次
hash[i-1]
必须大于等于 0。如果目标值hash[i] - 1
小于 0,则必须将当前频次降为 0,删除次数就是其原始值。
- 我们检查当前频次
- 返回结果:遍历完成后,
count
即为所需的最小删除次数。
复杂度
- 时间复杂度: O ( N + K log K + K ) = O ( N ) O(N + K \log K + K) = O(N) O(N+KlogK+K)=O(N)。
- O ( N ) O(N) O(N) 用于遍历字符串统计频次 (N 为字符串长度)。
- O ( K log K ) O(K \log K) O(KlogK) 用于排序频次数组 (K 为字符集大小,这里是 26,是常数)。
- O ( K ) O(K) O(K) 用于遍历频次数组处理冲突。
- 因为 K 是常数 (26),所以 K log K K \log K KlogK 和 K K K 都是 O ( 1 ) O(1) O(1)。因此,总时间复杂度由 O ( N ) O(N) O(N) 主导。
- 空间复杂度: O ( K ) = O ( 1 ) O(K) = O(1) O(K)=O(1)。
- 需要一个大小为 K (26) 的数组来存储频次。
Code
class Solution {public int minDeletions(String ss) {char[] s = ss.toCharArray();int[] hash = new int[26];for (char c : s) {hash[c - 'a']++;}Arrays.sort(hash);int count = 0;for (int i = 24; i >= 0; i--) {if (hash[i] == 0) {break;}if (hash[i] >= hash[i + 1]) {count += (hash[i + 1] - 1) <= 0 ? hash[i] : hash[i] - (hash[i + 1] - 1);hash[i] = Math.max((hash[i + 1] - 1), 0);}}return count;}
}