轉載本文章請標明作者和出處
本文出自《Darwin的程序空間》
本文題目和部分解題思路來源自《劍指offer》第二版
題目
輸入一個整型數組,數組裏有正數也有負數。數組中一個或連續的多個整數組成一個子數組。求所有子數組的和的最大值。要求時間複雜度爲O(n)。
例如:輸入的數組爲{1, -2, 3, 10, -4, 7, 2, -5},和最大的子數組爲{3, 10, -4, 7, 2}。因此輸出爲該子數組的和18。
解題分析
這道題,很多人可能想到的是窮舉出這個數組所有的非空子集,然後算出所有子集的和,在其中取最大值即爲答案,這我們也可以稱之爲暴力破解法;
但是一個數組的非空子集的個數爲n(n+1)/2個,這樣看的話,時間複雜度怎麼的也得在O(n^2),顯然,這是不符合題意的;
我們仔細審題,其實題目已經給了我們提示,既然他的時間複雜度的要求是O(n),所以這就間接說明了我們要在一次遍歷中搞定這個事情;雙層乃至三層for循環,就不要再想了;
既然是一遍循環就能搞定,那麼我們想一下,在遍歷的過程中,首先是1,那麼遍歷到這,我們所知道的最大值就是1,然後是-2,-2加1是-1,所以最大值還是1。然後是3,那麼要用-1加3麼?肯定不!因爲加上-1只會讓3變得小,所以3開始是不會再要前面加起來爲-1的子數組了,新的最長子數組應該從3爲頭計算,那麼3和之前最大的1比,明顯比1要大,所以最大值就變成3。後面是10,10加上3能變得更大,於是最大值變成了13,然後然後遇到了-4,13加上-4等於9,9沒有當前最大值13大,於是最大值還是13。然後後面是7,那麼7要和前面的【3,10,-4】在一起麼?爲什麼不呢?7加上前面的9更大了啊!所以現在的最長子串是【3,10,-4,7】,最大值是16,然後後面是2,最長子串變成了【3,10,-4,7,2】,最大值變成了18,然後最後是-5,18加-5等於13,並沒有18大,所以最後的結果是18;
精髓就是,每遍歷到一個節點,是都要用前面的子數組,取決於,前面的子數組是否大於0,你大於0我加上你就更大,我就要你,否則,我就成爲新的子數組的起點,我們沒遍歷一個節點就比較一下和當前最大值,最後就能求出連續子數組的最大和;
後面的結果和前面的結果息息相關,這是什麼思想呢?
我們定義兩個變量,一個max(用於記錄所求結果,最大的子數組和),一個temp用於記錄下一個節點前的最大連續和(用於判斷本節點要不要和前面的節點組成子數組還是單獨成爲起始節點)。
這兩個節點先圖同時指向第一個元素,max每次就負責和temp比較,一旦temp超過了它,就用temp替換掉它;因爲temp可能遇到一個負數,也有可能遇到正數,在temp變成負數之前不會被貴0,而max只負責記住最大的連續和;所以temp對下一個節點負責,max對整體的結果負責;
要特別注意的是,我們要重點考慮數組全爲負數的情況,此時應該返回最大的負數,做好代碼的健壯性;
代碼(JAVA實現)
ps:這裏筆者使用的jdk爲1.8版本、python解釋器爲3.7
測試數據即爲圖示數據;
- java
public class SubArray {
public static void main(String[] args) {
int[] arr = new int[]{1, -2, 3, 10, -4, 7, 2, -5};
int max = getMax(arr);
System.out.println(max);
}
private static int getMax(int[] arr) {
int max = arr[0];
int temp = arr[0];
for (int i = 1; i < arr.length; i++) {
if (arr[i] + temp < 0) {
if (arr[i] + temp > max) {
max = arr[i] + temp;
}
temp = 0;
continue;
}
if (arr[i] + temp > 0) {
temp += arr[i];
}
if (temp > max) {
max = temp;
}
}
return max;
}
}
- 動態規劃精簡版
public class SubArray {
public static void main(String[] args) {
int[] arr = new int[]{1, -2, 3, 10, -4, 7, 2, -5};
int max = getMax(arr);
System.out.println(max);
}
private static int getMax(int[] arr) {
int temp = arr[0];
int max = arr[0];
for (int i = 1; i < arr.length; i++) {
temp = Math.max(temp + arr[i], arr[i]);
max = Math.max(max, temp);
}
return max;
}
}
- python
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
max = nums[0]
sum = nums[0]
for i in range(1,len(nums)):
sum = sum + nums[i] if sum + nums[i] >= nums[i] else nums[i]
max = sum if sum > max else max
return max
題外話
其實這道題對比筆者來說是很重要的,因爲這是筆者做出的第一道算法題,是來成都之後一個面試官給我出的,當時我翻遍朋友全的大神,會算法的竟然屈指可數,當我做出來這道題之後,我高興了很久…
我把當時面試官的面試題貼出來,留一個紀念;