leetcode
leetcode 1901 ~ 1950
删除元素后和的最小差值

删除元素后和的最小差值

难度:

标签:

题目描述

代码结果

运行时间: 276 ms, 内存: 48.6 MB


/*
 * Problem: Given an integer array nums containing 3 * n elements,
 * you can delete exactly n elements, leaving 2 * n elements.
 * The remaining elements can be divided into two equal parts.
 * Find the minimum absolute difference between the sums of these two parts.
 */
import java.util.*;
import java.util.stream.IntStream;

public class MinDifferenceStream {
    public static int minDifference(int[] nums) {
        int n = nums.length / 3;
        int[] prefixSum = new int[3 * n + 1];
        IntStream.range(0, 3 * n).forEach(i -> prefixSum[i + 1] = prefixSum[i] + nums[i]);

        PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Collections.reverseOrder());
        int[] maxFirst = new int[2 * n + 1];
        IntStream.range(0, 2 * n).forEach(i -> {
            maxHeap.add(nums[i]);
            if (maxHeap.size() > n) maxHeap.poll();
            maxFirst[i + 1] = maxFirst[i] + maxHeap.peek();
        });

        PriorityQueue<Integer> minHeap = new PriorityQueue<>();
        int[] minSecond = new int[2 * n + 1];
        IntStream.range(0, 2 * n).map(i -> 2 * n - i - 1).forEach(i -> {
            minHeap.add(nums[3 * n - 1 - i]);
            if (minHeap.size() > n) minHeap.poll();
            minSecond[i + 1] = minSecond[i] + minHeap.peek();
        });

        return IntStream.range(0, n + 1).map(i -> maxFirst[n + i] - minSecond[i]).min().orElse(Integer.MAX_VALUE);
    }

    public static void main(String[] args) {
        int[] nums = {7, 9, 5, 8, 1, 3};
        System.out.println(minDifference(nums)); // Output: 1
    }
}

解释

方法:

为了求差值最小的两部分和,我们可以利用两个最小堆来维护每一种可能的前n和后n元素的和。这个解决方案首先定义一个辅助函数f,该函数使用最小堆来计算前n个元素的最大可能和。对于数组的前半部分,我们使用一个负数数组来将最小堆转换成最大堆,从而可以获得前n个元素的最大和。对于数组的后半部分,我们直接使用一个最小堆来维护后n个元素的最小和。这样,对于每个元素,我们可以快速更新堆和当前的和,如果新元素比堆顶要大。最后,通过比较所有可能的前后n元素和的组合,找到最小的差值。

时间复杂度:

O(n log n)

空间复杂度:

O(n)

代码细节讲解

🦆
为什么在计算前n个元素的最大和时,需要将元素值取负再使用最小堆?
在计算前n个元素的最大和时,我们需要找到前n个元素中最大的元素和。由于最小堆默认是提取出最小的元素,因此通过将所有元素取负数后,原本较大的元素会变成较小的负数,这样就可以利用最小堆的性质来维护这些原始元素的最大值。在堆中,最小的负数对应着原数组中的最大数。这样操作可以有效地使用标准库中的最小堆来实现最大堆的功能。
🦆
在函数f中,如果新元素比堆顶元素大时进行替换,这种操作的目的是什么?
在函数f中,如果新元素比堆顶元素大,进行替换的主要目的是更新当前的和以维持堆中的n个元素为当前遍历到的元素中的最大或最小n个。这样的替换确保了我们总是保持当前遍历的元素中的最大或最小累积和。替换操作后,堆重新调整,保持堆的性质,这使得我们能够快速计算出当前最优的和,并随着更多元素的遍历进行实时更新。
🦆
在计算后n个元素的最小和时,为什么选择对nums数组进行反转?
在计算后n个元素的最小和时,选择对nums数组进行反转是为了方便使用相同的处理逻辑来计算最小和。通过反转数组,原本数组的末尾n个元素变成了反转后数组的前n个元素,这样就可以复用前面定义的函数f,来计算这些元素的最小和。反转后,数组的遍历方式与处理前n个元素的方法相同,从而简化了代码的复杂性并保持了一致的处理逻辑。
🦆
如何保证f函数中的b数组能够正确记录每一步的和?
在f函数中,b数组用于记录每一步中的累积和。这是通过初始化一个堆和累积和变量,然后遍历元素来维护这个累积和实现的。在遍历过程中,每次堆的内容更新(如有新元素替换堆顶元素),累积和也相应地更新。这个累积和随后被记录在b数组中。具体地,b数组的每个位置记录了从数组开始到当前元素的最优和状态。通过这种方式,b数组能够准确地反映出在每个阶段堆中元素可能达到的最大或最小和。

相关问题