leetcode
leetcode 1801 ~ 1850
将数组分成两个数组并最小化数组和的差

将数组分成两个数组并最小化数组和的差

难度:

标签:

题目描述

给你一个长度为 2 * n 的整数数组。你需要将 nums 分成 两个 长度为 n 的数组,分别求出两个数组的和,并 最小化 两个数组和之 差的绝对值 。nums 中每个元素都需要放入两个数组之一。

请你返回 最小 的数组和之差。

 

示例 1:

example-1

输入:nums = [3,9,7,3]
输出:2
解释:最优分组方案是分成 [3,9] 和 [7,3] 。
数组和之差的绝对值为 abs((3 + 9) - (7 + 3)) = 2 。

示例 2:

输入:nums = [-36,36]
输出:72
解释:最优分组方案是分成 [-36] 和 [36] 。
数组和之差的绝对值为 abs((-36) - (36)) = 72 。

示例 3:

example-3

输入:nums = [2,-1,0,4,-2,-9]
输出:0
解释:最优分组方案是分成 [2,4,-9] 和 [-1,0,-2] 。
数组和之差的绝对值为 abs((2 + 4 + -9) - (-1 + 0 + -2)) = 0 。

 

提示:

  • 1 <= n <= 15
  • nums.length == 2 * n
  • -107 <= nums[i] <= 107

代码结果

运行时间: 1060 ms, 内存: 0.0 MB


// Java Stream solution
// 思路: 使用Java Stream的组合生成器来生成所有可能的分组. 由于Stream的特性, 我们可以简化生成组合和计算最小差值的过程.

import java.util.Arrays;
import java.util.stream.IntStream;

public class SolutionStream {
    public int minimumDifference(int[] nums) {
        int n = nums.length / 2;
        int totalSum = Arrays.stream(nums).sum();
        return IntStream.range(0, 1 << (2 * n))
                .filter(mask -> Integer.bitCount(mask) == n)
                .map(mask -> {
                    int sum = 0;
                    for (int i = 0; i < 2 * n; i++) {
                        if ((mask & (1 << i)) != 0) {
                            sum += nums[i];
                        }
                    }
                    return Math.abs(totalSum - 2 * sum);
                })
                .min()
                .orElse(0);
    }
}

解释

方法:

这个题解的思路是将数组分成左右两部分,分别求出左右两部分所有可能的子集和。然后对于左边每一个可能的子集和,在右边部分通过二分查找找到最接近总和一半的子集和,两者相加即可得到最接近总和一半的一种划分方式。所有划分方式中的最小值即为答案。

时间复杂度:

O(n*2^n)

空间复杂度:

O(2^n)

代码细节讲解

🦆
在算法中,为什么选择将原数组平分为两部分来计算子集和,而不是其他划分方式?
将数组平分为两部分来计算子集和是基于计算复杂度和实现的可行性的考虑。对于一个数组,其可能的子集数量是指数级增长的,即对于n个元素的数组,子集数为2^n。如果按照不平均分割,比如一大一小两部分,小部分虽然子集计算快但其组合的灵活性低,而大部分的子集计算则非常耗时且难以处理。平分数组可以较好地平衡这两部分的计算量,且每部分处理的子集数目相对平衡,这对后续的操作如比较和二分查找也更为有利。
🦆
题解中提到使用默认字典来存储子集和,这种数据结构相比普通字典有什么优势?
默认字典(defaultdict)在Python中相比普通字典的优势在于它提供了一个默认值,这使得当访问一个不存在的键时,不会抛出错误而是返回一个默认值。在处理子集和时,使用默认字典可以自动地为新的子集和分类初始化一个空集合,避免了额外的键存在检查和初始化代码,从而简化代码的复杂性并提高效率。
🦆
为什么在处理子集和时需要对每个集合的元素数量进行限制?
在处理子集和时对每个集合的元素数量进行限制主要是为了减少不必要的计算和存储。因为题目的目的是找出最小的差值,而这通常不需要考虑所有可能的子集组合,尤其是那些元素过多或过少的子集,它们对平衡总和的贡献较小。通过限制元素数量可以有效地减少处理的子集数量,从而提高算法的效率并减少内存使用。
🦆
二分查找在题解中的应用是如何确保可以找到最接近总和一半的子集和的?
在题解中,通过二分查找的应用旨在快速定位到与目标值(总和的一半)最接近的子集和。具体做法是先将其中一个部分的子集和排序,然后对于另一部分的每个子集和,计算出与总和一半相对应的目标子集和,使用二分查找在已排序的子集和中查找最接近这个目标的值。通过查找最接近的两个点(bisect_right返回的位置和前一个位置),可以比较哪个子集和与目标更接近,从而确保找到差值最小的组合。

相关问题