最大子陣列

2021-08-01 07:42:32 字數 4272 閱讀 4410

1.問題描述 

問題:乙個有n個整數元素的一維陣列(a[0],a[1],a[2],…a[n-1]),這個陣列中子陣列之和的最大值是多少? 

該子陣列是連續的。例如 陣列:[1,-2,3,5,-3,2]返回8; 陣列:[0,-2,3,5,-1,2]返回9。

網上有稱之為最大子串行和,亦有稱連續子陣列最大和。個人覺得叫最大子串行和不太妥,數學上講,子串行不一定要求連續,而這裡我們的題目必然要求是連續的,如果不連續而求子序列最大和很顯然就無意義了,這也是為啥又稱連續子陣列最大和。不過,莫要在意細節。

這題是很經典的一道面試題,也有各種解法,從演算法分析上,時間複雜度也有很大差別,下面我就給出三種不同的解法。

2.解法一:暴力列舉法 

此種方法最簡單,我想應該也是每個人拿到題目想到的第一種解法了,學過一點程式設計的人都應該能編出此類程式。

記sum[i..j]為陣列中第i個元素到第j個元素的和(其中0<=i

int maxsubarray(int *a,int n)
此種方法的時間複雜度為o(n2),顯然不是一種很好的辦法,也不是公司面試希望你寫出這樣的程式的。

3.解法二:分支界定 

這裡再介紹一種更高效的演算法,時間複雜度為o(nlogn)。這是個分治的思想,解決複雜問題我們經常使用的一種思維方法——分而治之。

而對於此題,我們把陣列a[1..n]分成兩個相等大小的塊:a[1..n/2]和a[n/2+1..n],最大的子陣列只可能出現在三種情況: 

a[1..n]的最大子陣列和a[1..n/2]最大子陣列相同; 

a[1..n]的最大子陣列和a[n/2+1..n]最大子陣列相同; 

a[1..n]的最大子陣列跨過a[1..n/2]和a[n/2+1..n] 

前兩種情況的求法和整體的求法是一樣的,因此遞迴求得。

第三種,我們可以採取的方法也比較簡單,沿著第n/2向左搜尋,直到左邊界,找到最大的和maxleft,以及沿著第n/2+1向右搜尋找到最大和maxright,那麼總的最大和就是maxleft+maxright。 

而陣列a的最大子陣列和就是這三種情況中最大的乙個。 

偽**如下:

int maxsubarray(int *a,int l,int r) 

4.解法三:動態規劃(dp) 

我們考慮最後乙個元素arr[n-1]與最大子陣列的關係,有如下三種情況: 

(1)arr[n-1]單獨構成最大子陣列 

(2)最大子陣列以arr[n-1]結尾 

(3)最大子陣列跟arr[n-1]沒關係,最大子陣列在arr[0-n-2]範圍內,轉為考慮元素arr[n-2]

從上面我們可以看出,問題分解成了三個子問題,最大子陣列就是這三個子問題的最大值,現假設: 

(1) 以arr[n-1]為結尾的最大子陣列和為end[n-1] 

(2) 在[0-(n-1)]範圍內的最大子陣列和為all[n-1] 

如果最大子陣列跟最後乙個元素無關,即最大和為all[n-2](存在範圍為[0-n-2]),則解all[n-1]為三種情況的最大值,即all[n-1] = max。從後向前考慮,初始化的情況分別為arr[0],以arr[0]結尾,即end[0] = arr[0],最大和範圍在[0,0]之內,即all[0]=arr[0]。根據上面分析,給出狀態方程: 

all[i] = max

**如下:

/* dp base version*/

#define max(a,b) ( a > b ? a : b)

int maxsum_dp(int * arr, int

size)

; int all[30] = ;

end[0] = all[0] = arr[0];

for(int i = 1; i < size; ++i)

return all[size-1];

}

上述**在空間上是可以優化為o(1)的

/* dp base version*/

#define max(a,b) ( a > b ? a : b)

int maxsum_dp(int * arr, int

size)

return nall ;

}

/* dp ultimate version */

int maxsum_ultimate(int * arr, int size)

else

if(sum > maxsum)

}return maxsum;

}

其實上面的方法雖說是從dp推導出來的,但是寫完發現也是很直觀的方法,求最大和,那就一直累加唄,只要大於0,就說明當前的「和」可以繼續增大,如果小於0了,說明「之前的最大和」已經不可能繼續增大了,就從新開始,如此這樣。

5.問題擴充套件:返回最大子陣列始末位置 

這個問題是《程式設計之美》2.14的擴充套件問題,返回始末位置還是比較容易的,我們知道,每當當前子陣列和的小於0時,便是新一輪子陣列的開始,每當更新最大和時,便對應可能的結束下標,這個時候,只要順便用本輪的起始和結束位置更新始末位置就可以,程式結束,最大子陣列和以及其始末位置便一起被記錄下來了。

c++**如下:

#include

using

namespace

std;

template

class my_data_type

;template

void my_data_type::my_type_printf(void)

template

void my_data_type::max_sub_sum_dp(t *a, int length)

else

if (sum > max_sum)

}}template

void printf_data(t *a, int length)

cout

<< endl;

}int main()

; int a1_int[6] = ;

float a_float[6] = ;

float a1_float[5] = ;

my_data_type my_test_int;

my_test_int.max_sub_sum_dp(a_int, 6);

printf_data(a_int, 6);

my_test_int.my_type_printf();

my_test_int.max_sub_sum_dp(a1_int, 5);

printf_data(a1_int, 5);

my_test_int.my_type_printf();

my_data_type my_test_float;

my_test_float.max_sub_sum_dp(a_float, 6);

printf_data(a_float, 6);

my_test_float.my_type_printf();

my_test_float.max_sub_sum_dp(a1_float, 5);

printf_data(a1_float, 5);

my_test_float.my_type_printf();

}

測試結果: 

6.問題擴充套件:允許陣列首尾相連 

這個也是2.14的擴充套件問題,如果陣列arr[0],…,arr[n-1]首尾相鄰,也就是允許找到一段數字arr[i],…,arr[n-1],arr[0],…,a[j],使其和最大,該如何?

程式設計之美解法:這個問題的解可以分為兩種情況:

1) 解沒有跨越arr[n-1]到arr[0] (原問題)

2) 解跨越arr[n-1]到arr[0]

對於第一種情況按照之前的方式計算即可,對於第二種情況我們可以巧妙地 進行問題轉換。我們找最大子陣列的對偶問題——最小子陣列,有了最小子陣列的值,總值減去它不就可以了麼?但是我又想,這個對偶問題只能處理這種跨界的特殊情況嗎?答案是肯定的,如果最大子陣列跨界,那麼剩餘的中間那段和就一定是最小的,而且和必然是負的;相反,如果最大子陣列不跨界,那麼總值減去最小子陣列的值就不一定是最大子陣列和了,例如例子[8,-10,60,3,-1,-6],最大子陣列為[8 | 60,3,-1,-6],而最小子陣列和為[-10],顯然不能用總值減去最小值。 

故,在允許陣列跨界(首尾相鄰)時,最大子陣列的和為下面的最大值 

maxsum=。

最大子陣列

給定乙個整數陣列,找到乙個具有最大和的子陣列,返回其最大和。樣例1 輸入 2,2,3,4,1,2,1,5,3 輸出 6 解釋 符合要求的子陣列為 4,1,2,1 其最大和為 6。樣例2 輸入 1,2,3,4 輸出 10 解釋 符合要求的子陣列為 1,2,3,4 其最大和為 10。要求時間複雜度為o ...

最大子陣列

import math 暴力 defforce array list 暴力取得最大子陣列 param array 引數陣列 return 最大的連續和 for i in range len array max num array i for j in range i 1 len array ifsu...

最大子陣列

給定乙個整數陣列,找到乙個具有最大和的子陣列,返回其最大和。樣例給出陣列 2,2,3,4,1,2,1,5,3 符合要求的子陣列為 4,1,2,1 其最大和為6 注意子陣列最少包含乙個數 挑戰要求時間複雜度為o n 1 假設前k個數的和sum已經計算出來 2 對於第k 1個數來說,如果sum 0,則說...