算法题 数组拆分

561. 数组拆分

问题描述

给定一个长度为 2n 的整数数组 nums,将数组中的元素两两配对,形成 n 个数对 (a₁, b₁), (a₂, b₂), ..., (aₙ, bₙ),使得所有数对中较小值的总和最大。

返回这个最大总和。

示例

示例 1:

输入:nums = [1,4,3,2]
输出:4
解释:所有可能的分法(忽略元素顺序)为:
1. (1, 4), (2, 3) -> min(1, 4) + min(2, 3) = 1 + 2 = 3
2. (1, 3), (2, 4) -> min(1, 3) + min(2, 4) = 1 + 2 = 3
3. (1, 2), (3, 4) -> min(1, 2) + min(3, 4) = 1 + 3 = 4
所以最大总和为 4

示例 2:

输入:nums = [6,2,6,5,1,2]
输出:9
解释:最优的分法为 (2, 1), (2, 5), (6, 6). min(2, 1) + min(2, 5) + min(6, 6) = 1 + 2 + 6 = 9

算法思路

核心

要使所有数对中较小值的总和最大,需要尽量减少大数被"浪费"为较大值的情况

贪心策略

  1. 排序数组:将数组按升序排列
  2. 贪心配对:每次取最小的两个数配对
    • 这样较小的数不会被更大的数"压制"
    • 避免小数与大数配对导致小数被选中的情况

数学证明

  • 排序后数组为:a₁ ≤ a₂ ≤ ... ≤ a₂ₙ
  • 最优策略是配对:(a₁,a₂), (a₃,a₄), ..., (a₂ₙ₋₁,a₂ₙ)
  • 结果为:a₁ + a₃ + ... + a₂ₙ₋₁
  • 为什么最优?
    • a₁ 必须被选中(它在任何配对中都是较小值)
    • 在剩余数中,a₃ 是能被选中的最小可能值
    • 以此类推…

代码实现

import java.util.Arrays;

class Solution {
    /**
     * 计算数组拆分后数对中较小值的最大总和
     * 
     * @param nums 长度为2n的整数数组
     * @return 所有数对中较小值的最大总和
     */
    public int arrayPairSum(int[] nums) {
        // 步骤1:对数组进行升序排序
        // 这是贪心算法的基础,确保能按最优方式配对
        Arrays.sort(nums);
        
        int maxSum = 0;  // 记录最大总和
        
        // 步骤2:贪心配对策略
        // 遍历排序后的数组,每隔一个元素取一个(取偶数位置的元素)
        // 即取索引为 0, 2, 4, ..., 2n-2 的元素
        for (int i = 0; i < nums.length; i += 2) {
            maxSum += nums[i];  // 每个数对中的较小值
        }
        
        return maxSum;
    }
}

算法分析

  • 时间复杂度:O(n log n)

    • 主要开销在排序操作
    • 后续遍历为 O(n)
  • 空间复杂度:O(1)

    • 只使用常数额外空间
    • (假设排序算法使用原地排序)
  • 优化

    • 贪心策略避免了复杂的动态规划
    • 一次排序解决全局最优问题

算法过程

nums = [1,4,3,2]

1:排序

原始数组: [1,4,3,2]
排序后:   [1,2,3,4]

2:贪心配对

  • 配对1: (1,2) → 较小值 = 1
  • 配对2: (3,4) → 较小值 = 3

3:计算总和

总和 = 1 + 3 = 4

验证其他配对方式

  1. (1,3),(2,4) → 1 + 2 = 3
  2. (1,4),(2,3) → 1 + 2 = 3
  3. (2,3),(1,4) → 2 + 1 = 3

最大值确实是 4。

测试用例

public static void main(String[] args) {
    Solution solution = new Solution();
    
    // 测试用例1:标准示例
    int[] nums1 = {1,4,3,2};
    System.out.println("Test 1: " + solution.arrayPairSum(nums1)); // 4
    
    // 测试用例2:重复元素
    int[] nums2 = {6,2,6,5,1,2};
    System.out.println("Test 2: " + solution.arrayPairSum(nums2)); // 9
    // 排序后: [1,2,2,5,6,6]
    // 配对: (1,2),(2,5),(6,6) → 1+2+6 = 9
    
    // 测试用例3:已排序数组
    int[] nums3 = {1,2,3,4,5,6};
    System.out.println("Test 3: " + solution.arrayPairSum(nums3)); // 9
    // 配对: (1,2),(3,4),(5,6) → 1+3+5 = 9
    
    // 测试用例4:逆序数组
    int[] nums4 = {6,5,4,3,2,1};
    System.out.println("Test 4: " + solution.arrayPairSum(nums4)); // 9
    
    // 测试用例5:所有元素相同
    int[] nums5 = {1,1,1,1};
    System.out.println("Test 5: " + solution.arrayPairSum(nums5)); // 2
    // 配对: (1,1),(1,1) → 1+1 = 2
    
    // 测试用例6:两个元素
    int[] nums6 = {1,2};
    System.out.println("Test 6: " + solution.arrayPairSum(nums6)); // 1
}

关键点

  1. 贪心选择的正确性

    • 最小的数必须被选中(它在任何配对中都是较小值)
    • 为了让总和最大,应该让次小的数不被选中
    • 所以将最小和次小的数配对
  2. 配对策略

    • 排序后取偶数索引位置的元素(0,2,4,…)
    • 这些位置的元素就是每个数对中的较小值
  3. 边界情况处理

    • 数组长度保证为偶数
    • 元素范围:-10⁴ ≤ nums[i] ≤ 10⁴
  4. 为什么不能用其他配对方式?

    • 任何其他配对都会导致某个较小值被"浪费"
    • 或者让较大值被迫成为较小值

常见问题

  1. 为什么排序后取偶数索引?

    • 因为排序后 nums[0] ≤ nums[1],所以 (nums[0],nums[1]) 的较小值是 nums[0]
    • 同理,(nums[2],nums[3]) 的较小值是 nums[2]
    • 所以取索引 0,2,4,… 的元素
  2. 如果数组长度不是偶数怎么办?

    • 题目保证长度为 2n,所以一定是偶数
    • 不需要额外处理
  3. 能否用动态规划解决?

    • 可以,但时间复杂度会更高(O(n²))
    • 贪心算法是最优解
  4. 负数如何处理?

    • 同样适用,排序后负数会排在前面
    • 贪心策略仍然成立
    • 例如 [-2,-1,1,2] → 配对 (-2,-1),(1,2) → 和 = -2+1 = -1
  5. 算法的直观理解

    • 想象有2n个人按身高排队
    • 要两两配对跳舞,每对取较矮者的身高求和
    • 为了让总和最大,应该让身高相近的人配对
    • 这样不会让很矮的人拉低很高的人
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值