前言
就有要把leetcode的題刷完,每天一道題,每天進步一點點
從零打卡leetcode之day 3
題目描述:
給定一個int型別的陣列,求最大子序列的和。
也就是說,從這個陣列中擷取一個子陣列,這個子陣列的元素和最大。
例如:
-1 20 -4 14 -4 -2
這個陣列的最大字序列和為30。即20 -4 14。
解題
1.初級版解法
對於這道題,其實我們可以採取遍歷所有可能的組合,然後再比較哪種組合的和最大。
也就是說,我們可以找出所有子序列,然後逐個比較。程式碼如下。
public int solve(int[] arrs){
int max = 0;//用來存放目標子序列的和
int temp = 0;//用來存每個子序列的和
for(int i = 0; i < arrs.length; i++){
for(int j = i; j < arrs.length; j++){
temp = 0;
//計運算元序列的和
for(int k = 0; k < arrs.length; k++){
temp += arrs[k];
}
//進行比較
if(temp > max){
max = temp;
}
}
}
return max;
}`
在這三個迴圈中,外面兩個迴圈列舉出所有子序列,第三個迴圈計運算元序列的和。
看到三個for迴圈,時間複雜度的O(n3)。這速度,實在是太慢了。我們來優化優化。
2.進階版
其實,你仔細看一下里面的那兩層for迴圈,會發現其實可以把它們合併成一個for迴圈的。
也就是說,我們可以在列舉所有子序列的過程中,是可以一邊進行資料處理的。還是直接看程式碼好理解點。如下:
public int solve2(int[] arrs){
int max = 0;
int temp = 0;
for(int i = 0; i < arrs.length; i++){
temp = 0;
for(int j = i; j < arrs.length; j++){
//一邊處理資料
temp += arrs[j];
//進行比較選擇
if(max < temp){
max = temp;
}
}
}
return temp;
}
該方法用了兩個for迴圈,時間複雜度為O(n2),相對來說好了一點。
3.再次優化進階
這次,我們可以使用遞迴的思想來處理。遞迴最重要的就是要找到:
- 遞迴的結束條件
- 把問題分解成若干個子問題。
對於這道題,其實我們可以把序列分成左右兩部分。那麼,最大子序列和的位置會出現在以下三種情況:
- 子序列完全在左半部分。
- 子序列完全在右半部分。
- 一部分在左,一部分在右。
所以我們只要分別求出左半部分的最大子序列和、右半部分的最大子序列和(注意,問題已經轉化為求左右兩部分的最大子序列和了,也就是說問題被分解成若干子問題了)、以及跨越左右兩部分的最大子序列和。最後比較三者之中哪個比較大就可以了。
如何求解左半部分和右半部分的最大子序列?
其實道理一樣,把左半部分和右半部分再次分解左右兩部分就可以了。
那麼,如何求解跨越左右兩部分的最大子序列呢?
其實只要求出包含左半部分中最右邊元素的子序列的最大和,以及求出包含右半部分中最左邊元素的子序列的最大和,然後讓兩者相加,即可求出跨域左右兩部分的最大子序列和了。
子問題已經分解出來了,那麼遞迴的結束條件是什麼?
我們把陣列分成左右兩部分,其實當左右兩部分只有一個元素時,遞迴結束。
這道題的遞迴思路算是找出來了,不過,程式碼實現?假如你對遞迴不大熟悉的話,可能在實現上還是有那麼點困難的。對於遞迴的學習,大家也可以看我寫的關於遞迴與動態規劃的幾篇文章。
我就直接拋程式碼了。
//遞迴版本
public int solve3(int[] arrs, int left, int right){
int max = 0;
//表示只有一個元素,無需在分解
if(left == right){
//為什麼?因為低於0的數肯定不可以是最大值的
//大不了最大值為0
max = arrs[left] >= 0 ? arrs[left]:0;
}else{
int center = (left + right)/2;
//求解左半部分最大子序列
int leftMax = solve3(arrs, left, center);
//求解右半部分最大子序列
int rightMax = solve3(arrs, center+1, right);
//求解kua跨越左右兩部分的最大子序列
//1.求包含左部分最右元素的最大和
int l = 0;
int l_max = 0;
for(int i = center; i >= left; i--){
l += arrs[i];
if(l > l_max){
l_max = l;
}
}
//2.求包含右部分最左元素的最大和
int r = 0;
int r_max = 0;
for(int i = center+1; i <= right; i++){
r += arrs[i];
if(r > r_max){
r_max = r;
}
}
//跨越左右兩部分的最大子序列
max = l_max + r_max;
//取三者最大值
if(max < leftMax) max = leftMax;
if(max < rightMax) max = rightMax;
}
return max;
}
遞迴求解方法的時間複雜度為O(nlgn)。這速度,比第一種做法,不知道快了幾個級別….
遞迴解法可以說是很快的了
但是,等等,我還是不滿意…
4.最終版:動態規劃
接下來的最終版,時間複雜度可以縮減到O(n), 雖然說是採用了動態規劃的思想,不過,我覺得你沒學過動態規劃也可以看懂。
假如我給你
1 2 -4 5 6
五個元素,你在計算前面三個元素的時候,即
1 + 2 + -4 = -1
發現前面三個元素的和是小於0的,那麼,這個
1 2 -4
的子序列我們還要嗎?顯然,這個子序列的和都小於0了,我們是可以直接淘汰的。因為如果還要這個子序列的話,它和後面的5一相加,結果變成了4,我們還不如讓我們的目標子序列直接從5開始呢。
先看程式碼吧,可能反而會好理解點
//基於動態規劃的思想
public int solve4(int[] arrs){
int max = 0;//存放目標子序列的最大值
int temp = 0;//存放子序列的最大值
for(int i = 0; i < arrs.length; i++){
temp += arrs[i];
if(temp > max){
max = temp;
}else{
//如果這個子序列的值小於0,那麼淘汰
//從後面的子序列開始算起
if(temp < 0){
temp = 0;
}
}
}
return max;
}
這道題不是leetcode上的題目,不過我覺得這道題很不錯,所以拿出來分享給大家。
完
對付的對手