每日算法-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;}
}
