leetcode
leetcode 2251 ~ 2300
使子数组元素和相等

使子数组元素和相等

难度:

标签:

题目描述

You are given a 0-indexed integer array arr and an integer k. The array arr is circular. In other words, the first element of the array is the next element of the last element, and the last element of the array is the previous element of the first element.

You can do the following operation any number of times:

  • Pick any element from arr and increase or decrease it by 1.

Return the minimum number of operations such that the sum of each subarray of length k is equal.

A subarray is a contiguous part of the array.

 

Example 1:

Input: arr = [1,4,1,3], k = 2
Output: 1
Explanation: we can do one operation on index 1 to make its value equal to 3.
The array after the operation is [1,3,1,3]
- Subarray starts at index 0 is [1, 3], and its sum is 4 
- Subarray starts at index 1 is [3, 1], and its sum is 4 
- Subarray starts at index 2 is [1, 3], and its sum is 4 
- Subarray starts at index 3 is [3, 1], and its sum is 4 

Example 2:

Input: arr = [2,5,5,7], k = 3
Output: 5
Explanation: we can do three operations on index 0 to make its value equal to 5 and two operations on index 3 to make its value equal to 5.
The array after the operations is [5,5,5,5]
- Subarray starts at index 0 is [5, 5, 5], and its sum is 15
- Subarray starts at index 1 is [5, 5, 5], and its sum is 15
- Subarray starts at index 2 is [5, 5, 5], and its sum is 15
- Subarray starts at index 3 is [5, 5, 5], and its sum is 15 

 

Constraints:

  • 1 <= k <= arr.length <= 105
  • 1 <= arr[i] <= 109

代码结果

运行时间: 120 ms, 内存: 33.2 MB


/*
题目思路:
1. 使用Stream计算每个位置的子数组总和。
2. 使用Stream计算目标总和。
3. 使用Stream计算总运算次数。
*/

import java.util.stream.IntStream;

public class Solution {
    public int minOperations(int[] arr, int k) {
        int n = arr.length;
        int[] subarraySum = new int[n];
        IntStream.range(0, n).forEach(i -> 
            IntStream.range(0, k).forEach(j -> 
                subarraySum[i] += arr[(i + j) % n]
            )
        );
        int totalSum = IntStream.of(subarraySum).sum();
        int targetSum = totalSum / n;
        int totalOperations = IntStream.of(subarraySum)
            .map(sum -> Math.abs(sum - targetSum))
            .sum();
        return totalOperations;
    }
}

解释

方法:

此题解采用了基于余数分组的策略,考虑了循环数组的特性和子数组总和的均等性。具体方法是通过计算数组长度n与子数组长度k的最大公约数g,将原数组根据余数分为g组,每组的元素是从数组索引r开始,每隔g个元素取一个形成的子数组。由于要求每个长度为k的子数组的和相同,这实际上转化为了确保在所有循环中跨越数组的这些固定偏移集合中的元素总和相同。对每个分组计算将其中所有元素调整为中位数的代价,然后累计这些代价以得到最终答案。选择中位数是因为中位数是最小化绝对偏差之和的最优策略,这在统计学中是一个众所周知的事实。

时间复杂度:

O(n log(n/k))

空间复杂度:

O(n)

代码细节讲解

🦆
为什么选择通过计算最大公约数来将数组分组?这种分组方法与题目要求的每个长度为k的子数组和相等有什么直接关系?
通过计算最大公约数g来分组的原因是,这样可以保证每组中的元素间隔g个元素,确保了这些元素在任何长度为k的子数组中均匀分布。由于长度为k的子数组需要覆盖整个原数组,且k可能不是n的因子,g作为n和k的最大公约数保证了每个子数组中的每个分组的元素可以均匀出现。这样,只要确保每个分组的元素总和相等,就可以保证所有长度为k的子数组的总和相等。
🦆
在选择中位数作为目标值时,为什么中位数是最优的选择?存在哪些统计或数学理论支持这种选择?
选择中位数作为目标值是因为中位数是统计学中一个重要概念,用于最小化一组数的绝对偏差之和。在数学理论中,这被称为最优化L1范数问题,即中位数是使得从一组数据到该点的绝对距离之和最小的点。这种性质使得在调整数组元素以使其和相等时,总的调整代价最小。
🦆
在计算调整到中位数的总代价时,函数是如何处理数组长度为偶数的情况?是否选择了上中位数还是下中位数,这种选择对结果有何影响?
在给定的代码中,当数组长度为偶数时,选择的是下中位数(即中间位置的前一个元素)。这是因为在Python的列表切片中,使用整数除法(//)自动向下取整。选择上中位数或下中位数可能会在某些特定情况下影响总代价,但在大多数情况下,这种差异对于总体代价是微小的。选择下中位数简化了计算方法,且仍然能有效地减小总偏差。
🦆
算法中递归深度的考虑基于何种原理?gcd函数调用的递归深度在最坏情况下会达到多少?
算法中对递归深度的考虑基于gcd函数的递归实现。Euclid算法(最常用的求gcd的方法)的递归深度取决于两个数字的大小及其关系,最坏的情况发生在两个连续的Fibonacci数,此时递归深度可以达到O(log(min(a, b))),其中a和b是gcd函数的输入。因此在实际应用中,递归深度通常是可管理的,因为输入的数值范围通常有限。

相关问题