53. 最大子序和(C++)
目錄
題目
給定一個整數陣列 nums ,找到一個具有最大和的連續子陣列(子陣列最少包含一個元素),返回其最大和。
示例:
輸入: [-2,1,-3,4,-1,2,1,-5,4]
輸出: 6
解釋: 連續子陣列 [4,-1,2,1] 的和最大,為 6。
分析與題解
暴力迴圈
比較容易想到的是暴力解題,即窮舉所有的子區間:
- 使用雙層迴圈,窮舉所有的子區間;
- 然後再對子區間內的所有元素求和;
- 時間複雜度是立方級別
程式碼如下:
class Solution { public: int maxSubArray(vector<int>& nums) { int n = nums.size(); int max = INT_MIN; for(int i=0;i<n;i++){ int sum=0; for(int j=i;j<n;j++){ sum += nums[j]; if(sum > max) max = sum; } } return max; } };
需要注意邊界的問題:首先在外層迴圈遍歷陣列,作為求非連續數列的起點,然後從此出發逐個元素相加,每新增一個新的元素,sum值進行變化都需要與最終的結果進行比較,取較大值,最終一直新增到元素末位。
動態規劃(狀態轉移方程1)
將問題切分為最優子問題進行求解。
①定義狀態
我們使用一個數組或者雜湊表dp儲存子問題的答案。例如dp[i]
代表以nums[i]
結尾的連續子陣列的最大和。
②思考轉移狀態方程
根據狀態的定義,由於 nums[i]
一定會被選取。並且 dp[i]
所表示的連續子序列與 dp[i - 1]
所表示的連續子序列(有可能)就差一個 nums[i]
。
先考慮最簡單的情況,假設陣列 nums 全是正數,那麼一定有 dp[i] = dp[i - 1] + nums[i]
但在一般情況下 dp[i - 1] 有可能是負數,例如前幾個數都是負數,突然來了一個正數。於是分類討論:
- 如果
dp[i - 1] >= 0
,那麼可以把nums[i]
直接接在dp[i - 1]
表示的那個陣列的後面。 - 如果
dp[i - 1] < 0
,那麼加上前面的數反而越來越小了,不如以nums[i]
為起點作為dp[i]
數值。
狀態轉移方程即為:
\[d p[i]=\left\{\begin{array}{ll} d p[i-1]+n u m s[i], & \text { if } \quad d p[i-1] \geq 0 \\ \text { nums }[i], & \text { if } \quad d p[i-1]<0 \end{array}\right.\]
程式碼如下:
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int n = nums.size();
int sum = 0;
int result = INT_MIN;
for(int i=0;i<n;i++){
sum+=nums[i];
result = max(result, sum);
if(sum < 0)
sum = 0;
}
return result;
}
};
自底向上(狀態轉移方程2)
對於dp[i-1]
的討論,最終無非是求子序列和的最大值,因此狀態轉移方程可以簡寫為:
\[d p[i]=\max \{n u m s[i], d p[i-1]+n u m s[i]\} \]
因此我們不再討論dp[i-1]的正負,直接將是/否新增dp[i-1]的值比較並取較大值。程式碼如下:
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int n = nums.size();
if(n==0) return 0;
vector<int> dp(n);
dp[0] = nums[0];
int result = dp[0];
for(int i=1;i<n;i++){
dp[i] = max(dp[i-1]+nums[i], nums[i]);
result = max(result, dp[i]);
}
return result;
}
};
需要注意的是狀態dp代表的是以nums[i]結尾的子序列和的最大值,但是對於所給序列的最大值並不一定以包含末尾元素,所以需要設定額外的變數來進行記錄和比較。