找到所有连续子阵列最大差值的和(S)的最佳方法

时间:2015-06-07 21:04:16

标签: arrays algorithm optimization subset

  

您将获得一个包含n个元素的数组:d[0], d[1], ..., d[n-1]。   计算所有连续子阵列最大差值的和(S)。

     

形式上:S = 总和{max {d [l,...,r]} - min {d [l,...,r}} ,∀0 <= l &lt; = r&lt;   Ñ

输入:

4 
1 3 2 4

输出:

12

解释

  

l = 0; r = 0;数组:[1] sum = max([1]) - min([1])= 0

     

l = 0; r = 1;数组:[1,3] sum = max([1,3]) - min([1,3])= 3 - 1 = 2

     

l = 0; r = 2;数组:[1,3,2] sum = max([1,3,2]) - min([1,3,2])= 3 - 1   = 2

     

l = 0; r = 3;数组:[1,3,2,4] sum = max([1,3,2,4]) - min([1,3,2,4])=   4 - 1 = 3

     

l = 1; r = 1将导致零

     

l = 1; r = 2;数组:[3,2] sum = max([3,2]) - min([3,2])= 3 - 2 = 1;

     

l = 1; r = 3;数组:[3,2,4] sum = max([3,2,4]) - min([3,2,4])= 4 -   2 = 2;

     

l = 2; r = 2;将导致零

     

l = 2; r = 3;数组:[2,4] sum = max([2,4]) - min([2,4])= 4 -2 = 2;

     

l = 3; r = 3将导致零;

     

总和= 12

我的想法: 对所有可能的子集进行暴力检查;传染性阵列。

How to optimize it for larger number?

2 个答案:

答案 0 :(得分:1)

假设您有一个长度为 n 的序列,并且您希望计算某些固定大小的 m&lt;滑动窗口的最小值(或最大值)。 Ñ。然后(令人惊讶),this can be done in O(n) time

现在,对于窗口大小 m = 1,...,n ,您需要从左到右运行滑动窗口;对于窗口的每个幻灯片,您只需要在窗口内添加元素的最大 - 最小值。通过上述,运行时间是 Theta(n ^ 2)。这改善了您的朴素算法 Theta(n ^ 3)

答案 1 :(得分:1)

这可以在线性时间内完成!对于每个子阵列,每个元素进入一次总和,它是最大值,每个子元素的每个元素减去一次,它是最小值。我们需要一个线性时间算法来查找每个元素的最大值或最小值的子阵列数,并且我们可以通过对all nearest smaller values算法的微小修改来实现这一点。

我们的想法是,为了找到一个元素最多的子数组,我们保持一堆我们看到的元素大于我们看到的所有后续元素,以及这些数字的位置。当我们发现一个元素大于堆栈中的最后一个元素时,我们知道一个子数组可以扩展到堆栈顶部元素的左侧或右侧,并且它仍然是最大值,我们可以使用它来确定最多的子阵列数。我们可以通过简单地否定数组的所有元素来处理最小值。

def max_sums(d):
    stack = [(-1, float('inf'))]
    sum_ = 0
    for i, x in enumerate(d):
        while x > stack[-1][1]:
            prev_i, prev_x = stack.pop()
            prev_prev_i, prev_prev_x = stack[-1]
            sum_ += prev_x * (i - prev_i) * (prev_i - prev_prev_i)
        stack.append((i, x))
    while len(stack) > 1:
        prev_i, prev_x = stack.pop()
        prev_prev_i, prev_prev_x = stack[-1]
        sum_ += prev_x * (len(d) - prev_i) * (prev_i - prev_prev_i)
    return sum_

def max_differences_sum(d):
    return max_sums(d) + max_sums([-x for x in d])

这是算法的示例运行。假设输入为[30, 10, 40, 20]。然后,为了计算所有子数组的最大值之和,我们按如下方式迭代输入:

30

我们将对(0, 30)推入堆栈。堆栈现在记录我们在索引0处看到30。

10

30 > 10,因此我们将对(1, 10)推入堆栈。堆栈现在记录我们在索引1处看到10。

40

10 < 40,因此最大10的子数组不能包含此元素。我们看到一个最大值为10的子数组必须在索引为30之后开始并在索引为40之前结束,因此它有一个可能的左端点和一个可能的右端点,并且有1*1这样的数组。我们将10*1*1添加到总和中并从堆栈中弹出(1, 10)。总和现在是10。

30 < 40,因此最大30的子数组也不能包含此元素。我们看到一个最大值为30的子数组必须从索引0开始并以索引0或索引1结束,因此有1*2个这样的数组。我们将30*1*2添加到总和中并弹出(0, 30)。总和现在是70。

现在堆栈已空,因此我们推送(2, 40)

20

40 > 20,因此我们推送(3, 20)

我们已遍历所有输入,因此对于仍在数组上的任何对(i, x),具有max x的子数组可以从索引i到数组末尾的任何位置结束,它可以从i开始到前一个堆栈条目的索引(如果没有上一个条目,则可以从数组的开头开始)。

(3, 20)位于堆栈中,其下方有(2, 40),因此最大20的子数组必须以索引3开头和结尾。我们将20*1*1添加到总和中pop (3, 20)。总和现在是90。

(2, 40)位于堆栈中,其下方没有任何内容,因此最大40的子数组可以从任何索引开始&lt; = 2并以任何索引结束&gt; = 2.我们添加{{ 1}}到总和并清空堆栈。总和现在是330。

我们已经计算了总和中的所有正数项。为了减去最小值,我们否定所有输入元素并再次通过上述算法提供它们。我们最终减去170,总计160。