每日算法-250427
每日算法 - 2024年4月27日
记录今天学习和解决的LeetCode算法题。
561. 数组拆分 (Array Partition)
题目描述

思路
贪心
解题过程
问题的目标是最大化 n 个 min(a_i, b_i) 对的总和。
假设我们有一对数 (a, b) 且 a <= b,那么 min(a, b) = a。为了让总和最大化,我们应该尽可能地让每一对中的较小数 a 尽可能大。
考虑将数组 nums 排序。排序后,我们得到 nums[0] <= nums[1] <= nums[2] <= ... <= nums[2n-1]。
如果我们把 nums[0] 和 nums[1] 配对,min(nums[0], nums[1]) = nums[0]。
如果我们把 nums[2] 和 nums[3] 配对,min(nums[2], nums[3]) = nums[2]。
以此类推,将 nums[2i] 和 nums[2i+1] 配对,min(nums[2i], nums[2i+1]) = nums[2i]。
可以证明,这种配对方式(即排序后将相邻元素配对)可以得到最大的 min 值之和。直观地想,如果我们不这样配对,例如将 nums[0] 与 nums[2] 配对,那么 nums[1] 就必须与更大的数配对,这可能会导致 min 值变小。
因此,最优策略是:
- 对数组
nums进行升序排序。 - 将排序后数组中下标为偶数(0, 2, 4, …)的元素加起来,即为最终答案。
复杂度
- 时间复杂度: O ( N log N ) O(N \log N) O(NlogN), 主要是排序所需的时间。
- 空间复杂度: O ( log N ) O(\log N) O(logN) or O ( N ) O(N) O(N), 取决于排序算法使用的额外空间(如果是原地排序,可以认为是 O ( 1 ) O(1) O(1),但递归栈可能需要 O ( log N ) O(\log N) O(logN))。通常计为 O ( 1 ) O(1) O(1) 或 O ( log N ) O(\log N) O(logN)。
Code
class Solution {public int arrayPairSum(int[] nums) {Arrays.sort(nums);int n = nums.length;int sum = 0;for (int i = 0; i < n; i += 2) {sum += nums[i];}return sum;}
}
2144. 打折购买糖果的最小开销 (Minimum Cost of Buying Candies With Discount)
题目描述

思路
贪心
解题过程
我们需要最小化购买糖果的总开销。优惠是“买二送一”,我们可以免费获得一组三个糖果中最便宜的那个。为了最大化优惠力度,我们应该让免费赠送的糖果尽可能昂贵。
贪心策略:
- 将所有糖果按价格
cost升序排序。 - 从最贵的糖果开始考虑。每次选择当前最贵的三个糖果组成一组。
- 在这一组中,我们支付价格最高和第二高的糖果的费用,价格第三高的糖果(也就是这一组里最便宜的)则免费获得。
- 重复这个过程,直到所有糖果都被处理完。
具体实现:
- 对
cost数组进行排序。 - 从数组末尾(最贵的糖果)开始,每三个元素为一组进行处理 (
i = n-1, n-2, n-3, 然后i = n-4, n-5, n-6, …)。 - 在每组
(cost[i], cost[i-1], cost[i-2])中,我们将cost[i]和cost[i-1]加入总开销sum,而cost[i-2]是免费的,所以跳过。 - 注意处理边界情况:如果最后剩余的糖果不足三个(剩一个或两个),那么这些剩余的糖果都需要购买,因为无法触发“买二送一”的优惠。代码中的循环
i -= 3和判断i == 0的情况处理了这一点。
复杂度
- 时间复杂度: O ( N log N ) O(N \log N) O(NlogN), 主要是排序所需的时间。
- 空间复杂度: O ( log N ) O(\log N) O(logN) or O ( N ) O(N) O(N), 取决于排序算法。
Code
class Solution {public int minimumCost(int[] cost) {int n = cost.length;if (n == 1) {return cost[0];} else if (n == 2) {return cost[0] + cost[1];}Arrays.sort(cost);int sum = 0;for (int i = n - 1; i >= 0; i -= 3) {sum += cost[i];if (i > 0) { sum += cost[i - 1]; }}return sum;}
}
3397. 执行操作后不同元素的最大数量 (Maximum Number of Distinct Elements After Operations)
题目描述

思路
贪心
解题过程
目标是经过操作后,数组中不同元素的数量最大化。每个元素 nums[i] 可以变为 [nums[i] - k, nums[i] + k] 区间内的任意整数。
为了使不同元素的数量尽可能多,我们希望相邻的元素尽可能不同。
贪心策略:
- 排序: 首先对数组
nums进行升序排序。这使得我们可以更容易地处理相邻元素。 - 从右往左处理: 我们可以从数组的右端(最大值)开始向左处理。这样做的好处是,当我们决定
nums[i]的新值时,nums[i+1]的新值已经确定了,我们可以利用这个信息来最大化nums[i]和nums[i+1]不同的可能性。 - 确定
nums[i]的新值:- 对于
nums[n-1](最大的元素),我们可以将其变为nums[n-1] + k,使其尽可能大。 - 对于
nums[i](i < n-1),我们希望它的新值nums'[i]满足:nums[i] - k <= nums'[i] <= nums[i] + k(操作约束)nums'[i] < nums'[i+1](尽可能与右侧元素不同)- 为了给左边的元素
nums[i-1]留出更多空间,我们希望nums'[i]尽可能大,但要小于nums'[i+1]。
- 因此,
nums'[i]的理想目标是min(nums[i] + k, nums'[i+1] - 1)。 - 同时,
nums'[i]不能小于nums[i] - k。 - 所以,最终
nums'[i] = Math.max(nums[i] - k, Math.min(nums[i] + k, nums'[i+1] - 1))。 - 代码中
nums[i]直接被修改后的值覆盖,x是原始值nums[i],y是nums[i+1]修改后的值减 1。
- 对于
- 计数: 在修改
nums[i]后,检查nums[i] < nums[i+1]是否成立。如果成立,说明i和i+1位置的元素是不同的,计数器ret增加。 - 结果: 循环结束后,
ret记录了有多少对相邻元素是不同的。不同元素的总数是ret + 1(因为n个元素之间有n-1个间隙,ret个不同间隙意味着ret+1个不同的段/元素)。
优化:
如果一个数 x 可以变成 [x-k, x+k] 区间内的数,这个区间的大小是 2k + 1。如果数组中所有 n 个元素都能被分配到不同的值,那么最大不同数就是 n。这种情况在 2k + 1 >= n 时是有可能实现的(即使所有原始 nums[i] 都相同,它们也可以分散成 n 个不同的数,只要可用的范围 2k+1 足够大)。因此,如果 2k + 1 >= n,我们可以直接返回 n。
复杂度
- 时间复杂度: O ( N log N ) O(N \log N) O(NlogN), 主要是排序所需的时间。贪心处理过程是 O ( N ) O(N) O(N)。
- 空间复杂度: O ( log N ) O(\log N) O(logN) or O ( N ) O(N) O(N), 取决于排序算法。
Code
class Solution {public int maxDistinctElements(int[] nums, int k) {int n = nums.length;if (k * 2 + 1 >= n) {return n;}Arrays.sort(nums);nums[n - 1] += k;int i = n - 2;int ret = 0;while (i >= 0) {int x = nums[i];int y = nums[i + 1] - 1;nums[i] = Math.max(Math.min(x + k, y), x - k);if (nums[i] < nums[i + 1]) {ret++;}i--;}return ret + 1;}
}
