我最近接受了一次采访,在那里我被问到以下算法问题。我无法找到O(n)解决方案,也无法找到谷歌的问题。
给定一个整数的数组A [a_0 ... a_(n-1)](+ ve和-ve)。形成一个 数组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。如果a_i< 0我们必须考虑从2到m_i-1 + 1的所有m_i,并查看哪一个产生最大乘积之和。
请建议线性时间算法。
答案 0 :(得分:2)
假设您有以下数组:
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 )
如果我们不将值重置为2,那么这些k -ve值和m + ve值的乘积值应等于:
- ( (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)时间内预先计算总和数
编辑:正如Evgeny Kluev指出
注意:在计算后缀总和时,如果我们遇到零值,那么可以有多个这样的解决方案。
答案 1 :(得分:1)
感谢Abhishek Bansal和Evgeny Kluevfor的伪代码。 这是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;
}
}
}