Algorithm 给定数组A,形成数组M,使乘积之和(a1*m1+;…+;an*mn)最大
我最近接受了一次采访,被问到了以下算法问题。我无法找到一个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) 例子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}
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)时间内和的数组
编辑:正如埃夫根尼·克鲁耶夫所指出的那样
注意:在计算后缀和时,如果我们遇到零值,那么可能会有多个这样的解决方案。感谢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来计算决策点感谢这两个指针!(这和零值后缀总和)