🤖 Daily Challenge: Problem #1877 #33
Open
+160
−0
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
LeetCode 每日一题:1877. Minimize Maximum Pair Sum in Array
题目链接: https://leetcode.com/problems/minimize-maximum-pair-sum-in-array/
难度: Medium
解题过程
思考过程
我需要先获取题目信息,看看是什么题目。调用 get_problem_info 获取题目 1877 的详细信息。
思考过程
题目是 "Minimize Maximum Pair Sum in Array",需要将数组元素两两配对,使得最大的配对和最小化。数组长度为偶数,元素范围在 1 到 1e5 之间,n 最大为 1e5。
思考:如何配对才能使最大配对和最小?直觉是将最大和最小的配对,次大和次小的配对,以此类推。这样可以避免大的数字和大的数字配对,导致和很大。这是典型的贪心策略。
证明:假设数组排序后为 a0 ≤ a1 ≤ ... ≤ an-1。我们想要找到一种配对方式使得最大和最小。如果我们让最大和最小配对(a0 和 an-1),那么剩余的元素继续这样配对,得到的最大和会是 max(a0+an-1, a1+an-2, ...)。可以证明这是最优的。
更形式化的证明:假设存在一个最优配对,其中最大和最小的配对不是这样,那么我们可以调整配对使得结果不会变差。
因此算法步骤:
时间复杂度:排序 O(n log n),双指针 O(n)。空间复杂度:取决于排序算法,通常 O(log n) 或 O(1)。C++ 的 sort 通常是快排,空间复杂度 O(log n)。可以接受。
但是否有线性时间复杂度的算法?考虑到元素范围有限(1-1e5),可以使用计数排序,但 n 最大 1e5,计数排序 O(n + range) = O(1e5 + 1e5) = O(2e5),比排序快。但实现简单起见,直接用 sort 也可以。
考虑到题目提示 n ≤ 1e5,排序是合理的。不过,也可以实现两种策略:贪心(排序+双指针)和计数排序+双指针。
但题目是普通题目,使用 SolutionBase 模式。我决定提供两种策略:1. 排序+双指针;2. 计数排序+双指针。
首先,我们需要查看是否涉及数据结构,不需要。但为了安全,还是查看一下数据结构的实现,确保没有内存管理问题。不过本题只是整数数组,不涉及自定义数据结构。
现在,我们需要根据提供的类名、命名空间等生成代码。题目提供了 solution_class_name: "MinimizeMaximumPairSumInArraySolution", solution_class_base: "MinimizeMaximumPairSumInArray", test_class_name: "MinimizeMaximumPairSumInArrayTest", namespace: "problem_1877"。
使用普通题目示例的模式:SolutionBase 模式。我们需要定义函数签名:int minPairSum(vector& nums)。我们将实现两种策略。
先写头文件:包含 core.h,定义命名空间,类继承自 SolutionBase,其中 Func 是 std::function<int(vector&)>。然后声明 minPairSum 方法,构造函数。
源文件:实现两种策略的静态函数,在构造函数中注册策略,minPairSum 调用 getSolution()。
测试文件:使用参数化测试,测试示例和额外用例。
现在构造一些测试用例:
现在开始生成代码。
首先,我需要思考计数排序的实现。因为元素范围 1-1e5,可以创建一个大小为 100001 的计数数组,然后填充计数。接着使用双指针:i 从最小值开始,j 从最大值开始,但需要处理计数。我们可以用两个指针遍历计数数组:left 指向最小值的索引,right 指向最大值的索引。当 count[left] == 0 时 left++,count[right] == 0 时 right--。然后配对:取一对 (left, right),和 = left + right,更新最大值,然后两个计数各减1。继续直到所有元素配对完毕。
但需要注意,当 left == right 时,意味着所有剩余元素都是同一个值,此时配对只能在这个值内部配对(因为元素个数为偶数,count[left] 为偶数,可以两两配对,和都是 2left)。但这种情况实际上不会影响最大和,因为最大值是 2left,而 left 是当前最小值也是最大值(因为所有值相等)。但为了简化,我们可以直接处理:当 left == right 时,配对剩下的所有元素,最大和是 2left(因为所有对的和都是 2left),所以可以直接返回 max(current_max, 2*left)。但我们可以继续循环直到 count[left] 用完。
实现计数排序策略需要小心边界。
现在开始生成文件。先调用 generate_all_files。
但在生成之前,我需要确保命名空间和类名正确。根据题目信息,命名空间是 problem_1877,solution_class_name 是 MinimizeMaximumPairSumInArraySolution,solution_class_base 是 MinimizeMaximumPairSumInArray,test_class_name 是 MinimizeMaximumPairSumInArrayTest。
参考示例中,TwoSumSolution 继承自 SolutionBase,其中 Func 是 std::function<vector(vector&, int)>。这里 Func 应该是 std::function<int(vector&)>。
头文件应该包含 core.h,命名空间 leetcode 和 problem_1877。
现在开始生成。
思考过程
文件已生成。现在需要编译验证。调用 build_project。
思考过程
编译成功。现在运行测试。调用 run_tests。
思考过程
所有测试通过!很好。现在我们可以输出解题报告。
但我需要确保代码质量。看看源文件,我实现了多个策略,但有些策略可能冗余。solution2_final 的逻辑可能有问题吗?让我们检查一下:在计数排序策略中,我们使用了 pairs_to_consume = min(count[left], count[right]),但这里有一个问题:当 left == right 时,pairs_to_consume 是 count[left],但每个配对消耗两个元素,所以实际可以消耗 count[left] / 2 对。我的代码中处理了这种特殊情况:if (left == right) pairs_to_consume /= 2; 然后 count[left] -= pairs_to_consume; count[right] -= pairs_to_consume; 但这样会导致 count[left] 减少 pairs_to_consume(即 count[left]/2),而实际上应该减少 2 * pairs_to_consume(即 count[left])。让我们仔细想想。
假设 left == right,且 count[left] = 6。我们想配对 3 对(每对消耗两个元素)。pairs_to_consume 初始为 min(6,6) = 6。然后 pairs_to_consume /= 2 变为 3。然后 count[left] -= 3, count[right] -= 3 导致 count[left] 变为 3,但实际应该变为 0(因为消耗了 6 个元素)。不对!实际上我们应该消耗 6 个元素,即 3 对。所以 count[left] 应该减少 6。但我们的操作只减少了 3。所以逻辑有误。
更好的方法是:当 left == right 时,可以配对 count[left] / 2 对。但我们需要更新计数为 count[left] - 2 * pairs_to_consume。但我们已经将 pairs_to_consume 除以 2 了,所以需要 count[left] -= 2 * pairs_to_consume。但 count[left] 和 count[right] 是同一个,所以我们应该只更新一个。
让我们修复这个逻辑。实际上,我们可以这样处理:当 left == right 时,配对数量为 count[left] / 2,更新 count[left] %= 2(但 count[left] 是偶数,所以变为 0)。但我们只需要更新一次计数。
我们可以在消耗配对时分别处理 left == right 的情况。或者采用更简单的方法:使用双指针直接模拟配对过程,每次消耗一对,而不是一次消耗多个配对。由于最多有 1e5 个元素,配对数量最多 5e4,循环 5e4 次是可以接受的。但我们需要在计数数组中跳过空桶,但我们可以用 while 循环。
简化实现:使用两个指针遍历计数数组,每次消耗一对(一个来自 left,一个来自 right)。这样逻辑清晰,复杂度 O(n + M),其中 M=100001,但每次消耗一对可能会循环很多次,但最多 5e4 次,可以接受。
让我们重写 counting sort 策略,采用简单的一次消耗一对的方式。但为了性能,我们可以一次消耗多对,但为了简单,我们选择简单方法。
现在,虽然测试通过了,但可能存在隐藏 bug。我们应该修复它。让我们重新生成源文件,修复 counting sort 策略。
我将重新生成文件,但先思考一下。
我决定保留两种策略:排序+双指针(最简单且高效),以及计数排序+双指针(优化版本)。对于计数排序,我们可以实现一个简单的消耗一对的版本。
实现思路:
但这样可能会陷入无限循环,因为当 left == right 且 count[left] 为奇数时,最终会剩余一个元素,但 n 是偶数,所以每个值的计数都是偶数?不一定,但总计数是偶数,但某个特定值的计数可能是奇数。例如,[1,1,2,2] 计数分别为 2 和 2,都是偶数。如果某个值出现奇数次,那么另一个值也必须出现奇数次,总和为偶数。但配对时,left 和 right 可能不同,所以没问题。
最简单的做法是每次只消耗一对,不考虑批量消耗。这样逻辑简单,不会出错。
让我们实现 countingSortTwoPointers:
但这样当 left == right 且 count[left] 为 2 时,消耗一对后 count[left] 变为 0(先减一,再减一),然后 left 指针会因为 count[left] == 0 而移动。但我们需要确保不会重复减导致负数。实际上,当 left == right 时,我们消耗了一对(两个元素),所以应该减少 2。我们可以直接 count[left] -= 2。但要注意 count[left] 可能为 1?不可能,因为如果 left == right,说明当前桶中元素至少有两个(因为我们需要配对两个相同元素)。但 count[left] 可能为 1,但 left == right 且 count[left] == 1,意味着只剩下一个元素,但总元素数为偶数,所以不可能出现这种情况。但为了安全,我们可以直接 count[left]-- 两次。
让我们采用 count[left]--; count[right]--; 当 left == right 时,实际上减了两次,但 count[right] 和 count[left] 是同一个,所以相当于减了两次。但代码中我们显式地减了两次:一次在 count[left]--,一次在 count[right]--,但它们是同一个计数器,所以实际上减了两次。这样没问题。
但这样写不够清晰。我们可以改为:
如果 left == right,count[left] 减了两次,符合预期。
但这样当 count[left] 初始为 1 时,会变为 -1,但这种情况不会发生,因为总元素数为偶数,且 left == right 意味着所有剩余元素都是同一个值,那么计数必定是偶数。所以安全。
现在,我们需要考虑 left 和 right 指针移动:当我们减少计数后,如果 count[left] 变为 0,指针应该移动。但 while 循环会在下次迭代中跳过。这样实现是可行的。
现在,我们重新生成源文件,只保留两种策略:排序+双指针,以及计数排序+双指针(简单版本)。移除其他冗余策略。
另外,我们确保代码风格良好,没有多余注释。
现在重新生成。使用 force_regenerate=true。首先更新头文件,但头文件没问题。我们只更新源文件。
现在调用 generate_all_files,设置 force_regenerate=true。但需要同时提供头文件和测试文件内容。我们可以复用之前的头文件和测试文件,只修改源文件。
但为了确保一致性,我们重新生成所有三个文件,但头文件和测试文件不变,只是源文件更新。
我们需要先获取当前头文件和测试文件的内容吗?我们可以直接使用之前生成的内容,但为了安全,我们重新构造。
我将重新生成,使用 force_regenerate=true。
现在,先思考最终的解题报告。
解题思路:
现在生成最终的代码。
让我们编写源文件。
思考过程
文件已重新生成。现在编译验证。
思考过程
运行测试。
思考过程
所有测试通过。现在我们可以输出解题报告。
解题报告:LeetCode 1877. Minimize Maximum Pair Sum in Array
题目理解
给定一个长度为偶数 n 的整数数组
nums,需要将数组中的元素两两配对,形成 n/2 个数对,使得每个元素恰好出现在一个数对中。定义数对的和为两数相加,目标是最小化所有数对和中的最大值。算法思路
要最小化最大配对和,直觉上应该避免大数与大数配对,因为这样会产生很大的和。最优策略是:将最大值与最小值配对,次大值与次小值配对,依此类推。
正确性证明(贪心选择):
实现策略
提供了两种实现方法,核心思想相同,只是排序方式不同:
策略1:排序 + 双指针
策略2:计数排序 + 双指针
复杂度分析
关键点与注意事项
测试用例设计
总结
本题是典型的贪心算法问题,关键在于识别出最优配对策略。通过排序将问题简化,然后使用双指针高效配对。两种实现策略各有优劣:排序方法代码简洁,计数排序方法在特定条件下性能更优。在实际应用中,可根据数据特点选择合适的策略。
✅ 解题完成!代码已通过编译和测试。
本报告由 AI 自动生成,包含完整的思考过程和解题步骤。