【问题标题】:求所有可能子数组的最大差之和
【发布时间】:2022-01-14 12:32:02
【问题描述】:

从给定数组的连续子集中找出可能的最大差异之和。

给定一个包含 n 个非负整数(允许重复元素)的数组 arr[],从给定数组的连续子集中找出可能的最大差异之和。

假设 max(s) 表示任何子集“s”中的最大值,而 min(s) 表示集合“s”中的最小值。我们需要找到所有可能子集的 max(s)-min(s) 之和。

Input : arr[] = {1, 2, 3}
Output : result = 4

说明:

All possible subset and for each subset s,
max(s)-min(s) are as :
SUBSET    |  max(s) | min(s) | max(s)-min(s)
{1, 2}    |  2      |  1     |   1
{2, 3}    |  3      |  2     |   1
{1, 2, 3} |  3      |  1     |   2
Total Difference sum = 4
Note : max(s) - min(s) for all subset with 
single element must be zero.

约束:

Array size can be from 1 to 10 power 5, also each element in array can be from 1 to 10 power 5.

这是取自 here 的代码,但此代码检查所有可能的子集而不是连续的子集:

public static int MOD = 1000000007;
      
    // function for sum of max min difference 
    public static long maxMin (int arr[], int n) 
    {
        // sort all numbers
        Arrays.sort(arr);
          
        // iterate over array and with help of 
        // horner's rule calc max_sum and min_sum
        long min_sum = 0, max_sum = 0;
        for (int i = 0; i < n; i++)
        {
            max_sum = 2 * max_sum + arr[n - 1 - i];
            max_sum %= MOD;
            min_sum = 2 * min_sum + arr[i];
            min_sum %= MOD;
        }
      
        return (max_sum - min_sum + MOD)%MOD;
    }

那么如何只获得连续的子集并以更少的时间复杂度解决这个问题。

【问题讨论】:

  • “连续”位实际上是什么意思?如果你只是选择“整个数组”作为数组的一个连续子集,它包含一个最小值和一个最大值,并且它们的总和就是答案?
  • @AndyTurner,这里连续的意思是元素彼此相邻。例如,在数组 [1,2,3] 中,子集 [1,3] 在我的情况下无效,因为它们不相邻。有效的子集是 [1,2],[2,3],[1,2,3]
  • 正确。所以,整个数组是整个数组的一个连续(非严格)子集,它的 max 和 min 元素相差最大。
  • 我需要我的帖子中提到的所有最大最小差异的总和。根据您的评论,我了解数组 [1,2.3] 最大值为 3,最小值为 1,因此最大值 - 最小值 = 3-1=2。但就我而言,它应该是我帖子中提到的 4。
  • 出于好奇,这个问题在现实世界中是否有特定的应用?还是这是一项学术活动?

标签: java algorithm


【解决方案1】:

您可以使用stream 实现此目的:

public static int difference(int[] arr) {
    int size = arr.length;
    
    return IntStream.range(0, size)
            .flatMap(i -> IntStream.range(i + 1, size)
                    .mapToObj(j -> Arrays.stream(arr, i, j + 1).summaryStatistics())
                    .mapToInt(stat -> stat.getMax() - stat.getMin()))
            .sum();
}

或者,正如@kcsquared 所注意到的,您可以使用 2 个stream,一个用于最大和,另一个用于最小和,然后将它们相减。这种方法还避免了不必要的boxingunboxing

public static int difference2(int[] arr) {
    int size = arr.length;
            
    int max = IntStream.range(0, size)
            .flatMap(i -> IntStream.range(i + 1, size)
                    .map(j -> Arrays.stream(arr, i, j + 1).max().getAsInt()))
            .sum();
    
    int min = IntStream.range(0, size)
            .flatMap(i -> IntStream.range(i + 1, size)
                    .map(j -> Arrays.stream(arr, i, j + 1).min().getAsInt()))
            .sum();
    return max - min;
}

【讨论】:

    【解决方案2】:

    你可以在O(n)时空里做到这一点。

    技术是使用all nearest smaller values.的算法首先,将问题分成两部分:

    1. 求所有子数组最大值的总和
    2. 求所有子数组最小值的总和,然后从第一个总和中减去。

    除了将所有出现的“小于”替换为“大于”之外,这两个问题的解决方案是相同的,因此我将仅描述最小值的情况。

    对于数组的每个元素A[i],您可以问:“有多少子数组以A[i] 作为它们的最小元素?”为了处理重复,假设我们总是将子数组中最右边出现的最小元素作为“代表”元素。

    问题转化为找出在看到严格小于A[i] 的元素之前,我们可以去A[i]左边 多远,以及到右侧 多远> 的A[i] 我们可以在看到像A[i] 这样小的元素之前先走。将这两个距离相乘以获得以A[i] 作为其最小元素的子数组中左右端点的所有可能选择。我们可以使用“所有最接近的较小值”算法直接找到这两个,然后像这样解决剩下的问题(伪代码):

     1. For each position i in the array A, let previous_smaller[i]
        be the largest index j such that A[j] < A[i] and 0 <= j < i,
        or -1 if there is no such index.
    
     2. For each position i in the array A, let next_smaller_or_equal[i]
        be the smallest index j such that A[j] <= A[i] and i < j < n,
        or n if there is no such index.
    
     3. Return the sum over all i, 0 <= i < n, of 
        (A[i] * 
        (next_smaller_or_equal[i] - i) * 
        (i - previous_smaller[i]))
    

    例如,this question 的答案中有所有最接近的较小值 的几种实现方式,以及维基百科文章中的伪代码。要查找“下一个较小的值”而不是“上一个较小的值”,只需在反向数组 A 上运行算法(或以相反的顺序遍历 A,从 A[n-1] 向下到 A[0])。

    整个算法的示例实现(在 Python 中):

    def max_difference_sum(A: List[int]) -> int:
        """Given an array of integers A, compute the 
        sum over all subarrays B of max(B) - min(B)
        by using nearest smaller values"""
        
        n = len(A)
    
        # Convention to take the rightmost min or max in each subarray.
        previous_smaller = list(range(n))
        next_smaller_or_equal = list(range(n))
    
        previous_larger = list(range(n))
        next_larger_or_equal = list(range(n))
    
        # Compute the previous larger and smaller in a single loop.
        for i in range(n):
            j = i - 1
            while j >= 0 and A[j] >= A[i]:
                j = previous_smaller[j]
            previous_smaller[i] = j
    
            j = i - 1
            while j >= 0 and A[j] <= A[i]:
                j = previous_larger[j]
            previous_larger[i] = j
    
        for i in reversed(range(n)):
            j = i + 1
            while j < n and A[j] > A[i]:
                j = next_smaller_or_equal[j]
            next_smaller_or_equal[i] = j
    
            j = i + 1
            while j < n and A[j] < A[i]:
                j = next_larger_or_equal[j]
            next_larger_or_equal[i] = j
    
        max_sums = sum(A[i]
                       * (next_larger_or_equal[i] - i)
                       * (i - previous_larger[i])
                       for i in range(n))
    
        min_sums = sum(A[i]
                       * (next_smaller_or_equal[i] - i)
                       * (i - previous_smaller[i])
                       for i in range(n))
        
        return max_sums - min_sums
    

    【讨论】:

    • 嗨@kcsquared,请问如果索引超出范围(-1 或 n),请在步骤 3 中使用什么值?
    • @AndriySlobodyanyk 在这些情况下,您将使用 -1 或 n。我为整个算法添加了一些 Python 代码,希望能更清晰。
    【解决方案3】:

    让我们使用归纳法。

    1. 假设我们以某种方式解决了大小为 N 的数组的问题并知道所需的总和。
    2. 如果添加了元素 A[n+1],让我们找到解决方案。
    3. 我们只需要计算所有包含 A[n+1] 的序列的总和。
      • A[0], A[1], A[2], ..., A[n+1]
      • A[1], A[2], ..., A[n+1]
      • ...
      • A[n], A[n+1]
      所有其他连续子集都是在上一步以某种方式计算的。
    4. 为了计算它们的最小值和最大值,让我们以相反的顺序检查它们。
      • A[n+1], A[n]
      • A[n+1], A[n], A[n-1]
      • ...
      • A[n+1], A[n], ..., A[0]
      这使我们能够迭代它们并在单个循环中找到它们的极端。
    5. 所以代码是
      int a[] = {1, 2, 3};
      
      long sum = 0;
      for (int i = 1; i < a.length; i++) {
          int min = a[i];
          int max = a[i];
      
          for (int j = i - 1; j >= 0; j--) {
              int current = a[j];
              if (current < min) min = current;
              if (current > max) max = current;
              sum += max - min;
          }
       }
       
       System.out.println("Sum = " + sum);
      
    6. 解决方案复杂度为 O(n^2),因为有两个嵌套循环。

    【讨论】:

    • 我在从归纳方法到最终代码的过程中不知何故迷路了,介于第 3 步和第 5 步之间。不过,代码有效,所以原因一定是我的能力有限……
    【解决方案4】:

    由于提议的部分解决方案已经支付了排序成本,初始时间优化可以将输入 arr 预先转换为 (i, arr[i]) 列表,然后按 arr[i]值排序 & 在 for 循环中跳过sorted_tuple_arr 具有非连续的i 值。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-02-24
      • 2016-07-04
      • 1970-01-01
      • 1970-01-01
      • 2021-11-15
      相关资源
      最近更新 更多