Algorithm 给定数组A,形成数组M,使乘积之和(a1*m1+;…+;an*mn)最大

Algorithm 给定数组A,形成数组M,使乘积之和(a1*m1+;…+;an*mn)最大,algorithm,Algorithm,我最近接受了一次采访,被问到了以下算法问题。我无法找到一个O(n)解决方案,也无法在谷歌上找到问题所在 给定一个由整数(+ve和-ve)组成的数组A[A_0…A_(n-1)]。形成 数组M[M_0…M_(n-1)],其中M_0=2,M_i在[2,…,M_(i-1)+1] 这样乘积之和是最大的,即我们必须最大化a_0*m_0 +a_1*m_1+…+a(n-1)*m(n-1) 例子 input {1,2,3,-50,4} output {2,3,4,2,3} input {1,-1,8,12}

我最近接受了一次采访,被问到了以下算法问题。我无法找到一个O(n)解决方案,也无法在谷歌上找到问题所在

给定一个由整数(+ve和-ve)组成的数组A[A_0…A_(n-1)]。形成 数组M[M_0…M_(n-1)],其中M_0=2,M_i在[2,…,M_(i-1)+1] 这样乘积之和是最大的,即我们必须最大化a_0*m_0 +a_1*m_1+…+a(n-1)*m(n-1)

例子

input  {1,2,3,-50,4}
output {2,3,4,2,3}

input  {1,-1,8,12}
output {2,3,4,5}
我的O(n^2)解决方案是从m_0=2开始,只要a_i是+ve,就继续递增1。如果AAI i<0,我们必须考虑所有Mayi从2到MyI-1到1,并且看哪一个产生最大乘积和。
请推荐线性时间算法。

假设您有以下数组:

1, 1, 2, -50, -3, -4, 6, 7, 8.
在每次输入时,我们可以继续递增,也可以将值重置为较低的值。
这里只有两个好的选择。我们可以为当前条目选择最大可能值,也可以选择最小可能值(2)。(向末尾打样)

现在很清楚,我们输出的前3个条目应该是2、3和4(因为到目前为止所有的数字都是正数,没有理由将它们重置为2(低值)

遇到负值条目时,计算总和:
-(50+3+4)=-57

接下来计算后续+ve个连续数字的相似和。
(6+7+8)=21

由于57大于21,因此将第4个条目重置为2是有意义的

再次计算负分录的总和:
-(3+4)=-7

现在7小于21,因此不进一步重置是有意义的,因为如果正值较高,则应获得最大乘积

因此,输出阵列应为:

2, 3, 4, 2, 3, 4, 5, 6, 7
要使此算法在线性时间内工作,可以预先计算计算中需要的和的数组

证明:

当遇到负数时,我们可以将输出值重置为低值(如j)或继续递增(如i)

假设有k-ve值和m个正值

如果我们将该值重置为j,则这些k-ve值和m+ve值的乘积值应等于:

- ( (j-2+2)*a1 + (j-2+3)*a2 + ... + (j-2+k+1)*ak ) + ( (j-2+k+2)*b1 + (j-2+k+3)*b2 + ... + (j-2+k+m+1)*am )
- ( (i+2)*a1 + (i+3)*a2 + (i+4)*a3 ... + (i+k+1)*ak ) + ( (i+k+2)*b1 + (i+k+3)*b2 + ... + (i+k+m+1)*am )
如果我们不将该值重置为2,则这些k-ve值和m+ve值的乘积值应等于:

- ( (j-2+2)*a1 + (j-2+3)*a2 + ... + (j-2+k+1)*ak ) + ( (j-2+k+2)*b1 + (j-2+k+3)*b2 + ... + (j-2+k+m+1)*am )
- ( (i+2)*a1 + (i+3)*a2 + (i+4)*a3 ... + (i+k+1)*ak ) + ( (i+k+2)*b1 + (i+k+3)*b2 + ... + (i+k+m+1)*am )
因此,上述两个表达式之间的差异为:

(i-j+2)* ( sum of positive values - sum of negative values )
这个数字可以是正的,也可以是负的。因此,我们倾向于使j尽可能高(M[i-1]+1)或尽可能低(2)。

预计算O(N)时间内和的数组

编辑:正如埃夫根尼·克鲁耶夫所指出的那样

  • 向后遍历数组
  • 如果遇到负元素,请忽略它
  • 如果遇到正数,请使后缀和等于该值
  • 继续将元素的值加到和中,直到它保持为正
  • 一旦总和小于0,请注意这一点。这是将我们重置为2并继续递增的决定分开的点
  • 再次忽略所有负值,直到达到正值
  • 继续重复,直到到达数组末尾

  • 注意:在计算后缀和时,如果我们遇到零值,那么可能会有多个这样的解决方案。

    感谢Abhishek Bansal和Evgeny Klueffor的伪代码。 下面是Java中的代码

     public static void problem(int[] a, int[] m) {
            int[] sum = new int[a.length];
            if(a[a.length-1] > 0)
              sum[a.length-1] = a[a.length-1];
            for(int i=a.length-2; i >=0; i--) {
              if(sum[i+1] == 0 && a[i] <= 0) continue;
              if(sum[i+1] + a[i] > 0) sum[i] = sum[i+1] + a[i];
            }
            //System.out.println(Arrays.toString(sum));
            m[0] = 2;
            for(int i=1; i < a.length; i++) {
              if(sum[i] > 0) {
                m[i] = m[i-1]+1;
              } else {
                m[i] = 2;
              }
            }
          }
    
    公共静态无效问题(int[]a,int[]m){
    int[]总和=新的int[a.长度];
    如果(a[a.length-1]>0)
    和[a.length-1]=a[a.length-1];
    对于(int i=a.length-2;i>=0;i--){
    如果(和[i+1]==0&&a[i]0)和[i]=和[i+1]+a[i];
    }
    //System.out.println(Arrays.toString(sum));
    m[0]=2;
    for(int i=1;i0){
    m[i]=m[i-1]+1;
    }否则{
    m[i]=2;
    }
    }
    }
    
    我认为这是一个很好的方法。但为什么只重置为2?2是可能的,但它可以一直到M[I-1]+1。而且我认为它仍然是O(n^2)。当-ve时增加也没有意义,所以-(50*2+3*3+4*4)应该是-(50*2+3*2+4*2)。请不要认为允许的最小和最大M[I]是[2,M[I-1]+1]。@khallas算法是O(n)因为您可以预先计算和值并将其存储在数组中。至于正确性,老实说,我不确定。@khallas负值也可以递增。例如,如果数组为{1,-1,-1,50,50}然后我们会增加负值,因为接下来的正数(50)会有更高的输出值。你是对的,只有两个好的选项。获得正确算法所需的唯一缺少的细节是我们需要向后处理数组:跳过尾随的负数(和零)元素,然后在其仍然为正时计算后缀和,并重复这两个步骤,直到遇到数组的开头。顺便说一句,我们还可以跟踪零值后缀和,以回答相关问题:这个问题有多少不同的解决方案。@EvgenyKluev没有看到我们可以通过迭代Backar来计算决策点感谢这两个指针!(这和零值后缀总和)