每日算法打卡 - 2025年4月25日
记录今天完成的几道 LeetCode 算法题,分享解题思路和代码。
2178. 拆分成最多数目的正偶数之和
题目
解题思路
贪心算法
解题过程
题目要求我们将一个偶数 finalSum
拆分成尽可能多的 不同 正偶数之和。
为了使拆分出的数字数量最多,我们应该尽可能选择小的偶数。因此,可以采用贪心策略:从最小的正偶数 2 开始,依次尝试添加 4, 6, 8, …,并不断更新剩余的 finalSum
。
具体步骤如下:
- 初始化一个空列表
ret
用于存放结果。 - 首先判断
finalSum
是否为偶数,如果不是,无法拆分,直接返回空列表。 - 使用一个变量
i
从 2 开始,表示当前尝试添加的偶数。 - 进入循环,只要
finalSum
大于 0:
a. 尝试从finalSum
中减去当前的偶数i
。
b. 关键一步:检查减去i
后的finalSum
。如果finalSum <= i
,这意味着剩余的finalSum
不足以让我们在下一步添加i+2
(因为需要不同的偶数,且i+2 > i >= finalSum
),或者刚好等于i
(如果加上i
会导致重复)。为了用尽finalSum
并保证得到最多数量的偶数,我们将这个剩余的finalSum
加到当前的i
上,形成i + finalSum_remaining
。这个合并后的值将是最后一个加入列表的数。然后将finalSum
设为 0,表示总和已完全分配。
c. 将(可能被调整过的)i
添加到结果列表ret
中。
d. 将i
增加 2,准备处理下一个偶数。 - 当
finalSum
变为 0 时,循环结束,返回列表ret
。
这种方法确保了每次都取最小的可用偶数,从而最大化了偶数的数量,并且通过最后一步的合并操作保证了所有数都是不同的正偶数且总和恰好为 finalSum
。
复杂度分析
- 时间复杂度: O ( f i n a l S u m ) O(\sqrt{finalSum}) O(finalSum)。 我们依次添加 2, 4, 6, …, k。这些数的和大约是 k 2 / 2 k^2 / 2 k2/2。当和达到
finalSum
时停止,所以 k 2 ≈ 2 × f i n a l S u m k^2 \approx 2 \times finalSum k2≈2×finalSum, 即 k ≈ 2 × f i n a l S u m k \approx \sqrt{2 \times finalSum} k≈2×finalSum。循环的次数与 k k k 成正比,因此时间复杂度为 O ( f i n a l S u m ) O(\sqrt{finalSum}) O(finalSum)。 - 空间复杂度: O ( f i n a l S u m ) O(\sqrt{finalSum}) O(finalSum)。 结果列表
ret
最多存储约 f i n a l S u m \sqrt{finalSum} finalSum 个数。
Code
class Solution {public List<Long> maximumEvenSplit(long finalSum) {List<Long> ret = new ArrayList<>();if (finalSum % 2 != 0) {return ret;}long i = 2;while (finalSum > 0) {finalSum -= i;if (finalSum <= i) {i += finalSum;finalSum = 0;}ret.add(i);i += 2;}return ret;}
}
2567. 修改两个元素的最小分数
题目
解题思路
贪心
解题过程
题目要求我们通过修改数组中的两个元素,使得数组的“分数”(最大值与最小值的差)最小。我们有两次修改机会。
为了最小化最大值与最小值的差,最优的修改策略总是将待修改的元素值改为与数组中某个“目标”元素相等。由于我们可以修改两个数,我们可以考虑以下几种情况来消除极端值对分数的影响:
- 修改两个最大值: 将数组中最大的两个元素修改成与最小值
nums[0]
相等(或者修改成任何小于等于nums[n-3]
的值)。修改后,数组的实际最大值为nums[n-3]
,最小值为nums[0]
。分数是nums[n-3] - nums[0]
。 - 修改两个最小值: 将数组中最小的两个元素修改成与最大值
nums[n-1]
相等(或者修改成任何大于等于nums[2]
的值)。修改后,数组的实际最小值为nums[2]
,最大值为nums[n-1]
。分数是nums[n-1] - nums[2]
。 - 修改一个最大值和一个最小值: 将最小值
nums[0]
修改成nums[1]
(或更大),并将最大值nums[n-1]
修改成nums[n-2]
(或更小)。修改后,数组的实际最小值为nums[1]
,最大值为nums[n-2]
。分数是nums[n-2] - nums[1]
。
这三种情况涵盖了通过两次修改来最小化 max - min
的所有有效策略。因为我们总是希望消除最大的数或最小的数对分数的影响。
因此,我们首先对数组进行排序,然后计算上述三种情况对应的分数,取其中的最小值即为答案。
复杂度分析
- 时间复杂度: O ( N log N ) O(N \log N) O(NlogN),主要是数组排序所需的时间。
- 空间复杂度: O ( log N ) O(\log N) O(logN) 或 O ( 1 ) O(1) O(1),取决于排序算法使用的额外空间。
Code
class Solution {public int minimizeSum(int[] nums) {Arrays.sort(nums);int n = nums.length;int maxToMin = nums[n - 3] - nums[0];int minToMax = nums[n - 1] - nums[2];int maxToMinANDminToMax = nums[n - 2] - nums[1];return Math.min(maxToMinANDminToMax, Math.min(maxToMin, minToMax));}
}
1509. 三次操作后最大值与最小值的最小差
题目
解题思路
贪心
解题过程
这道题与上一题类似,但我们有三次修改机会。目标仍然是最小化修改后数组的最大值与最小值的差。
同样,最优策略是修改数组中的极端值(最大或最小的那些)。有三次修改机会,意味着我们可以“消除”数组排序后两端的最多三个元素对最终 max - min
差值的影响。考虑以下四种消除极端值的情况:
- 修改最大的三个数: 将
nums[n-1]
,nums[n-2]
,nums[n-3]
修改掉。剩下的元素范围是[nums[0], nums[n-4]]
。最小差值为nums[n-4] - nums[0]
。 - 修改最小的三个数: 将
nums[0]
,nums[1]
,nums[2]
修改掉。剩下的元素范围是[nums[3], nums[n-1]]
。最小差值为nums[n-1] - nums[3]
。 - 修改最小的两个数和最大的一个数: 将
nums[0]
,nums[1]
和nums[n-1]
修改掉。剩下的元素范围是[nums[2], nums[n-2]]
。最小差值为nums[n-2] - nums[2]
。 - 修改最小的一个数和最大的两个数: 将
nums[0]
,nums[n-1]
和nums[n-2]
修改掉。剩下的元素范围是[nums[1], nums[n-3]]
。最小差值为nums[n-3] - nums[1]
。
这四种情况覆盖了所有最优的可能。因为要最小化差值,我们总是改变最大或最小端的元素。改变中间的元素不会比改变两端的元素更优。
所以,先对数组排序。如果数组长度 n
小于或等于 4,我们总能通过三次修改使得所有元素相等,差值为 0。否则,计算上述四种情况的差值,返回其中的最小值。
复杂度分析
- 时间复杂度: O ( N log N ) O(N \log N) O(NlogN), 瓶颈在于排序。
- 空间复杂度: O ( log N ) O(\log N) O(logN) 或 O ( 1 ) O(1) O(1), 取决于排序算法。
Code
class Solution {public int minDifference(int[] nums) {int n = nums.length;if (n <= 4) {return 0;}Arrays.sort(nums);int maxToMin = nums[n - 4] - nums[0];int minToMax = nums[n - 1] - nums[3];int firstTwo = nums[n - 2] - nums[2];int lastTwo = nums[n - 3] - nums[1];int one = Math.min(maxToMin, minToMax);int two = Math.min(firstTwo, lastTwo);return Math.min(one, two);}
}