連續子數組最大和


1. 問題描述

輸入一個整形數組,求數組中連續的子數組使其和最大。比如,數組x

應該返回 x[2..6]的和187.

2. 問題解決

我們很自然地能想到窮舉的辦法,窮舉所有的子數組的之和,找出最大值。

窮舉法

i, j的for循環表示x[i..j],k的for循環用來計算x[i..j]之和。

maxsofar = 0
for i = [0, n)
    for j = [i, n)
        sum = 0
        for k = [i, j]
            sum += x[k]
        /* sum is sum of x[i..j] */
        maxsofar = max(maxsofar, sum)

有三層循環,窮舉法的時間復雜度為\(O(n^3)\)

對窮舉法的改進1

我們注意到x[i..j]之和 = x[i..j-1]之和 + x[j],因此在j的for循環中,可直接求出sum。

maxsofar = 0
for i = [0, n)
    sum = 0
    for j = [i, n)
        sum += x[j]
        /* sum is sum of x[i..j] */
        maxsofar = max(maxsofar, sum)

顯然,改進之后的時間復雜度變為\(O(n^2)\)

對窮舉法的改進2

在計算fibonacci數時,應該還有印象:用一個累加數組(cumulative array)記錄前面n-1次之和,計算當前時只需加上n即可。同樣地,我們用累加數組cumarr記錄:cumarr[i] = x[0] + . . . +x[i],那么x [i.. j]之和 = cumarr[j] -cumarr[i - 1]

cumarr[-1] = 0
for i = [0, n)
    cumarr[i] = cumarr[i-1] + x[i]
    
maxsofar = 0
for i = [0, n)
    for j = [i, n)
        sum = cumarr[j] - cumarr[i-1]
        /* sum is sum of x[i..j] */
        maxsofar = max(maxsofar, sum)

時間復雜度依然為\(O(n^2)\)

分治法

所謂分治法,是指將一個問題分解為兩個子問題,然后分而解決之。具體步驟如下:

  • 先將數組分為兩個等長的子數組a, b;

  • 分別求出兩個數組a,b的連續子數組之和;

  • 還有一種情況(容易忽略):有可能最大和的子數組跨越兩個數組;

  • 最后比較\(m_a\), \(m_b\), \(m_c\),取最大即可。

在計算\(m_c\)時,注意:\(m_c\)必定包含總區間的中間元素,因此求\(m_c\)等價於從中間元素開始往左累加的最大值 + 從中間元素開始往右累加的最大值

float maxsum3(l, u)
    if (l > u) /* zero elements */
        return 0
        
    if (l == u) /* one element */
        return max(0, x[l])
    
    m = (l + u) / 2
    /* find max crossing to left */
    lmax = sum = 0
    for (i = m; i >= l; i--)
        sum += x[i]
        lmax = max(lmax, sum)
    
    /* find max crossing to right */
    rmax = sum = 0
    for i = (m, u]
        sum += x[i]
        rmax = max(rmax, sum)

    return max(lmax+rmax,
                maxsum3(l, m),
                maxsum3(m+1, u));

容易證明,時間復雜度為\(O(n*log \ n)\)

動態規划

Kadane算法又被稱為掃描法,為動態規划(dynamic programming)的一個典型應用。我們用DP來解決最大子數組和問題:對於數組\(a\),用\(c_i\)標記子數組\(a[0..i]\)的最大和,那么則有

\[c_i = \max \{ a_i, c_{i-1} + a_i \} \]

子數組最大和即為\(\max c_i\)。Kadane算法比上面DP更進一步,不需要用一個數組來記錄中間子數組和。通過觀察容易得到:若\(c_{i-1} \leq 0\),則\(c_i = a_i\)。用\(e\)表示以當前為結束的子數組的最大和,以替代數組\(c\);那么

\[e = \max \{ a_i, e + a_i \} \]

Python實現如下:

def max_subarray(A):
    max_ending_here = max_so_far = A[0]
    for x in A[1:]:
        max_ending_here = max(x, max_ending_here + x)
        max_so_far = max(max_so_far, max_ending_here)
    return max_so_far

max_ending_here對應於標記\(e\)max_so_far記錄已掃描到的子數組的最大和。Kadane算法只掃描了一遍數組,因此時間復雜度為\(O(n)\).

3. 參考資料

[1] Jon Bentley, Programming Pearls.
[2] GeeksforGeeks, Largest Sum Contiguous Subarray.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM