【问题标题】:Better algorithm to calculate this formula更好的算法来计算这个公式
【发布时间】:2017-06-05 03:57:47
【问题描述】:

给定一个正整数数组A,我想找到以下总和S

S = 2^1 * (S_1) + 2^2 * (S_2) + 2^3 * (S_3) + ... + 2^n * (S_N)

其中S_iA 中连续i 整数的乘积之和,例如:

S_3 = (A[0]*A[1]*A[2]) + (A[1]*A[2]*A[3]) + .... + (A[n-3]*A[n-2]*A[n-1])


为了计算任何S_i,我使用类似于滚动哈希的方法,实现O(N)

Let tmp = A[0]*A[1]*A[2], S_3 = tmp;
For(i = 3 to n) 
    (tmp *= A[i]) /= A[i-3]
    S_3 += tmp

对于所有两个的力量,我可以预先进行预计算。所以现在我计算S的算法是O(N^2)

我的问题是,是否可以计算出更复杂的S

【问题讨论】:

  • 我认为你不能,因为你必须单独计算每一行,因为你不能做任何算术简化。每行需要 O(n) 并且有 n 行。我很确定 O(n^2 是绝对最小值)
  • (tmp *= A[i]) /= A[i-3] — 如果A[i-3] == 0,这不会失败吗?
  • @squeamishossifrage 都是正面的,抱歉我错过了

标签: algorithm math time-complexity


【解决方案1】:

您可以使用一种递归模式来计算具有线性时间复杂度的解决方案。

考虑一个示例输入:

     A = [2, 5, 3, 2, 1, 8]

现在看看只有第一个元素的部分数组:

    A0 = [2]

调用这个部分数组的结果R0。很明显:

    R0 = 2⋅A[0] = 2⋅2 = 4

然后考虑原始数组中多了一个值的数组:

    A1 = [2, 5]

调用这个部分数组的结果R1。我们计算得出:

    R1 = 2⋅(A[0]+A[1]) + 4⋅(A[0]⋅A[1]) = 54

我们可以尝试用R0来写这个,希望这样可以限制计算的次数:

    R1 = 2⋅(2⋅A[0] + 1)⋅A[1] + R0

用第三个元素再次扩展,我们得到:

    R2 = 2⋅(A[0]+A[1]+A[2]) + 4⋅(A[0]⋅A[1] + A[ 1]⋅A[2]) + 8⋅(A[0]⋅A[1]⋅A[2]) = 360

让我们试着用 R1 来写这个:

    R2 = 2⋅(2⋅(2⋅A[0] + 1)⋅A[1] + 1)⋅A[2] + R1

出现了一种模式,第一项看起来很像前面R中的第一项。它类似于 2⋅(X + 1)⋅A[2],其中 X 是前一个 R 的第一项。我们一般可以说:

    Rn = 2⋅(Rn-1 - Rn-2 + 1)⋅A[n] + Rn-1

...当 n 时,Rn 为 0。

现在这是可以在线性时间内计算出来的东西。这是 JavaScript 中的一个实现,它还包括第二个函数,该函数以幼稚、非优化的方式进行计算,以便可以比较结果:

function fastCalcS(a) {
    let r0, r1, r2;
    r1 = r2 = 0;
    for (let i = 0; i < a.length; i++) {
        r0 = r1;
        r1 = r2;
        r2 = 2*(r1 - r0 + 1)*a[i] + r1;
    }
    return r2;
}

// This function does it the slow way, and executes
// the literal definition of S without optimisation:
function slowCalcS(a) {
    let coeff = 2;
    let s = 0
    for (let i = 0; i < a.length; i++) {
        let s_i = 0;
        for (let j = 0; j < a.length-i; j++) {
            let prod = 1;
            for (let k = 0; k <= i; k++) {
                prod *= a[j+k];
            }
            s_i += prod;
        }
        s += coeff * s_i;
        coeff *= 2;
    }
    return s;
}

var a = [2, 5, 3, 2, 1, 8];
console.log('slow way:', slowCalcS(a));
console.log('fast way:', fastCalcS(a));

在提供 reduce 数组方法的语言中(就像 JavaScript 所做的那样),函数看起来像这样(ES6 语法):

function fastCalcS(a) {
    return a.reduce( ([r1, r2], v) => [r2, 2*(r2 - r1 + 1)*v + r2], [0, 0] )[1];
}

var a = [2, 5, 3, 2, 1, 8];
console.log(fastCalcS(a));

【讨论】:

    【解决方案2】:

    一种与 trincot 非常相似的方法,但从另一端开始导致另一种线性算法。

    如果我们只有数组 {A[n-1]} 那么总和将是:

    T[n-1] = 2*A[n-1]
    

    如果我们然后尝试 { A[n-2], A[n-1]} 我们得到

    T[n-2] = 2*A[n-2] + 2*A[n-1] + 4*A[n-2]*A[n-1]
           = T[n-1] + 2*A[n-2]*( 1 + 2*A[n-1])
    

    继续这样我们得到递归

    T[n-k] = T[n-k+1] + 2*A[n-k]* K[n-k+1]
    K[n-k] = 1 + 2*A[n-k]*K[n-k+1]
    

    在 C 中

    int64_t sum( int64_t n, const int64_t* A)
    {
    int64_t K = 1;
    int64_t T = 0;
    int64_t i = n;
        while( --i >= 0)
        {   T += 2*K*A[i];
            K = 1 + 2*A[i]*K;
        }
        return T;
    }
    

    【讨论】:

    • 好找到!!您可以通过将2*K*A[i] 存储在一个临时变量中来保存一些乘法,并在TK 的更新中使用它。
    猜你喜欢
    • 2022-09-25
    • 2012-12-18
    • 1970-01-01
    • 1970-01-01
    • 2011-01-22
    • 2018-06-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多