# [LeetCode] 53. Maximum Subarray (Medium)
###### tags: `LeetCode`
## 題目
給定一個array`nums`,找出加起來最大的連續subarray,回傳那個和。
## 限制條件
* `1 <= nums.length <= 10^5`
* `-10^4 <= nums[i] <= 10^4`
## 範例
* Example 1:
```python
輸入: nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4]
輸出: 6
```
* Example 2:
```python
輸入: nums = [1]
輸出: 1
```
* Example 3:
```python
輸入: nums = [5, 4, -1, 7, 8]
輸出: 23
```
## 解答
### Kadane 演算法
Kadane 演算法是一種類似動態規劃的做法。這個做法對array的所有元素掃描一次,在每個點上計算以該點做為結尾的最大subarray和。這個subarray由兩部分組成:以前一個位置為結尾的最大subarray、該位置的數值。由於以每個位置做為結尾的最大subarray都是基於其前一位置的最大subarray計算得出,因此這個做法滿足optimal substructure(最佳子結構),因此可看成動態規劃的一個例子。
那麼我們要怎麼以前一個位置的最大subarray和來計算出目前位置的最大subarray和呢?很簡單,如果前一個位置的最大subarray和大於0,就把目前的元素加進去;否則,新的最大subarray和就只包含目前的這個元素。然後,我們再保留目前為止看到的和裡面最大的和即可。
為了加快執行速度,我們也考慮2個最簡單的base case:當`nums`長度為1時,回傳那個唯一的元素;當`nums`長度為2時,我們回傳兩個元素本身還有兩個元素相加這三種可能的最大的那個。
我們的Python code實作如下:
```python=
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
if len(nums) > 2:
s = 0
max_s = - float("inf")
for i in range(len(nums)):
if s >= 0:
s += nums[i]
else:
s = nums[i]
max_s = max(s, max_s)
return max_s
else:
if len(nums) == 2:
return max(nums[0], nums[1], nums[0] + nums[1])
if len(nums) == 1:
return nums[0]
```
在這邊,`s`即為以目前元素做為結尾的最大subarray和,而`max_s`則是用來保留目前為止看到的最大subarray和。
Kadane演算法的時間複雜度可以很容易看出來是 $O(n)$,因為只有一個for迴圈,而裡面的所有步驟只需要常數時間。空間複雜度的部分,我們只用了2個參數(`s`和`max_s`),因此是 $O(1)$。可以看出,Kadane演算法是個很有效率的演算法。
### Divide and conquer
既然滿足optimal substructure,那代表其實我們也可以使用divide and conquer(分治法)的方式來寫出一個遞迴解。這個方法雖然比Kadane演算法沒效率一些,但仍有其討論價值。
Divided and conquer方式的核心就是把問題分解成較小的子問題,分別解決這些子問題,再透過結合這些子問題的解來獲得原問題的解。
對於這個問題,要怎麼把原問題分解成較小的子問題呢?我們可以把題目給定的`nums` array 切成左右兩半。這樣一來,相加起來最大的subarray可以有三種可能性:
1. 全部都在左半邊
2. 全部都在右半邊
3. 橫跨左右兩半
我們只要取這三種可能性中最大的結果,就會是最後的答案。前兩個case很簡單,只要使用遞迴方式求解就可以了。比較麻煩的是最後一個橫跨兩半的case。
要找到橫跨兩半的最大subarray也不難,只要分別找出包含交接點左右兩邊元素的subarray中最大的subarray,再把兩邊的subarray接起來即可:

以上就是使用divide and conquer解決這題的邏輯,剩下的工作就是把這些邏輯寫成Python code:
```python=
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
def helper(l, r):
if l > r:
return - float("inf")
m = int((l + r) / 2)
curr = best_left = best_right = 0
for i in range(m - 1, l - 1, -1):
curr += nums[i]
best_left = max(best_left, curr)
curr = 0
for i in range(m + 1, r + 1):
curr += nums[i]
best_right = max(best_right, curr)
best_comb = best_left + best_right + nums[m]
left_half = helper(l, m - 1)
right_half = helper(m + 1, r)
return max(left_half, right_half, best_comb)
return helper(0, len(nums) - 1)
```
在這邊我們定義一個`helper` function來實作遞迴的部分,這個function吃兩個參數:`l`和`r`,分別指示目前要尋找`nums` array部分的左邊界和右邊界。
這個演算法的時間複雜度是$O(n \log n)$,因為總共有$O(\log n)$次呼叫,每次都有一個$O(n)$的for迴圈。空間複雜度是$O(\log n)$,因為有$O(\log n)$個結果要儲存,每個佔用$O(1)$的空間。