在数组中索引,使其前缀sum等于其后缀sum - 最佳解决方案

时间:2016-03-26 02:37:57

标签: python algorithm performance loops big-o

问题

给出了由N个整数组成的零索引数组A.该数组的均衡指数是任何整数P,使得0 ≤ P < N和较低指数的元素之和等于较高指数的元素之和。

A[0] + A[1] + ... + A[P−1] = A[P+1] + ... + A[N−2] + A[N−1].

假设零元素的总和等于0.如果P = 0P = N−1,可能会发生这种情况。

N的范围:[0 ... 100,000]

元素范围:[−2,147,483,648 ... 2,147,483,647]

复杂性:最坏情况时间O(N)

我的5分钟解决方案

这是直观的解决方案,通过计算公式性能为O(N^2),因为它为每次迭代求和所有数组,并且它不适用于大型条目。

def solution(A):
    result = []
    for i in xrange(len(A)):
        if sum(A[:i]) == sum(A[i+1:]):
            result.append(i)
    if result == []:
        return -1
    return result

最佳解决方案

此解决方案为O(N)。有人可以解释这个解决方案背后的逻辑。

def equi(A):
    result = []
    x=1
    i=1
    r=sum(A)
    for e in A:
        i-=1
        r-=2*e
        if -e==r:
            result.append(-i)
    return result

3 个答案:

答案 0 :(得分:3)

我相信你发布的解决方案根本不起作用,反正很难理解。 所以我的看法就是这样:

def equi(v):
    left_sum = 0       # sum(v[:p]) at all times.
    right_sum = sum(v) # sum(v[p+1:]) at all times.

    for i in xrange(len(v)):
        right_sum -= v[i]
        if left_sum == right_sum:
            return i   # your p.
        left_sum += v[i]
    return -1 # Because, there may not be an equilibrium index at all.

基本上,不是在循环的每次迭代中重新计算sum(左侧)和sum(右侧),而是可以通过简单的数学计算它们。

在某种程度上说明了一个大小为n的数组:

pos1 = i
left_sum1 = v[0] + v[1] + ... + v[i-1]
right_sum1 = v[i+1] + v[i+2] + ... + v[n-1]

现在让我们前进一步,检查一下我们应该拥有什么:

pos2 = i+1
left_sum2 = v[0] + v[1] + ... + v[i]
right_sum2 = v[i+2] + v[i+2] + ... + v[n-1]

现在,改变了什么?

left_sum2 - left_sum1 = v[i]
right_sum2 - right_sum1 = -v[i+1]

这可能会令人困惑,但应该明白,通过添加和减去先前的值,可以通过某种方式获得左侧和右侧的总和。

答案 1 :(得分:1)

O(n)解决方案有点过于聪明并且有些混淆,但它的工作正常。我用有意义的变量名称重写了它,并移动了一些东西,使它更清楚它是如何工作的。

def equilibriums(A):               # line 1
    result = []                    # line 2
    difference = sum(A)            # line 3

    for p in range(len(A)):        # line 4

        difference -= 2*A[p]       # line 5

        if -A[p] == sum_of_tail:   # line 6
            result.append(p)       # line 7

    return result                  # line 8

现在解释一下。让

left  = 0,
right = A[0] + A[1] + ... + A[N-2] + A[N-1] = sum(A), and
difference = right - left = sum(A) - 0 = sum(A)    # <-- explains line 3

A[0]移除right并添加到left时,difference会降低2*A[0]。如果A[1]right移至left,则difference会降低2*A[1]。每当移动元素A[p]时,difference都会降低2*A[p]。这解释了第4和第5行。

现在,在均衡指数P,我们有:

A[0] + A[1] + ... + A[P−1] = A[P+1] + ... + A[N−2] + A[N−1]                # definition
                           = A[P+1] + ... + A[N−2] + A[N−1] + A[P] - A[P]  # add A[P]-A[P]
                           = A[P] + A[P+1] + ... + A[N−2] + A[N−1] - A[P]  # rearrange
\---- but this = left ---/   \--------- and this = right --------/

,或者

left = right - A[P]

difference = right - left                 # definition
           = right - (right - A[P])       # substitute
           = A[P]                         # simplify

如果我们将A[P]right移至left,则difference会降低2*A[P],现在

difference = A[P] - 2*A[P] = -A[P]

也就是说,当平衡点从right移动到left时,differenceA[P]变为-A[P]。因此,如果difference == -A[P],则P是均衡指数。这解释了第6行的测试。

注意,算法并不真正需要leftright。它们只是用于解释。

equilibriums([1,2,3,0,1,0,0,0,0,1,6])  ==> returns [5, 6, 7, 8]

答案 2 :(得分:0)

另一种方法如下:

  1. 考虑我们在任务开始时所知道的内容 - 我们知道输入的长度,即len(A),我们知道输入的总和即sum(A),我们也知道没有先前的金额已计算出来。因此,这些成为我们的初始变量

    sumA, prevSum, n = sum(A), 0, len(A)
    
  2. 接下来让我们考虑每次迭代会发生什么。我们声明一个变量,例如rem这是(sumA - prevSum -A[i])的值。本质上,在每次迭代中,我们希望从数组的总和中删除前一个总和(prevSumleftSum),并删除当前值。

  3. 然后我们检查是否rem == prevSum。如果这是True,我们返回索引,如果为false,我们重复此循环,直到此时检查了数组中的所有元素,我们返回一个Falsy值。

    此代码可用here