53.最大子序和-Java實現
阿新 • • 發佈:2021-01-25
技術標籤:LeetCode資料結構動態規劃leetcode演算法分治演算法
文章目錄
相關標籤
- 陣列
- 分治演算法
- 動態規劃
題目描述
給定一個整數陣列 nums ,找到一個具有最大和的連續子陣列(子陣列最少包含一個元素),返回其最大和。
示例:
輸入: [-2,1,-3,4,-1,2,1,-5,4]
輸出: 6
解釋: 連續子陣列 [4,-1,2,1] 的和最大,為 6。
進階:
如果你已經實現複雜度為 O(n) 的解法,嘗試使用更為精妙的分治法求解。
解法1:貪心演算法/動態規劃(√)
動態轉移方程:
f[i] = max{ nums[i] , f[i-1] + nums[i] }
初始條件:
f[0] = nums[0]
程式碼:
class Solution {
public int maxSubArray(int[] nums) {
int pre = 0;
int sum = nums[0];
for (int i = 0; i < nums.length; i++) {
// 如果前i項的和小於第i項的值 則將pre置為當前項的值
pre = Math.max(pre + nums[ i] , nums[i]);
sum = Math.max(sum , pre);
}
return sum;
}
}
時間複雜度:O(n),空間複雜度:O(1)
執行用時:1ms
解法2:分治演算法(√)
class Solution {
public int maxSubArray(int[] nums) {
return maxSubArraySum(nums, 0, nums.length-1);
}
/**
* 計算[left,right]的最大子序和
* @param nums
* @param left
* @param right
* @return
*/
public static int maxSubArraySum(int[] nums, int left, int right) {
if (left == right) {
return nums[left];
}
int mid = (left + right) / 2;
// 如果最大子序和落在mid左邊,計算[left,mid]的最大子序和
int maxLeftSum = maxSubArraySum(nums, left, mid);
// 如果最大子序和落在mid右邊,計算[mid+1,right]的最大子序和
int maxRightSum = maxSubArraySum(nums, mid + 1, right);
// 如果最大子序和跨中點mid
int maxMidSum = findMaxCrossingSubArray(nums, left, mid, right);
// 返回左、中、右三個值中最大值即為最大子序和
return Math.max(Math.max(maxLeftSum,maxRightSum),maxMidSum);
}
/**
* 計算跨中點mid的最大子序和,分別計算從mid到左和從mid+1到右的最大子序和相加
* @param nums
* @param left
* @param mid
* @param right
* @return
*/
public static int findMaxCrossingSubArray(int[] nums, int left, int mid, int right) {
//類似尋找最大最小值的題目,初始值一定要定義成理論上的最小最大值(可能為負陣列)
// maxLeftBorderSum :從 mid 往左找到的最大子序和
// maxRightBorderSum :從 mid+1 往右找到的最大子序和
int maxLeftBorderSum = Integer.MIN_VALUE, maxRightBorderSum = Integer.MIN_VALUE, sum = 0;
for (int i = mid; i >= left; --i) {
sum +=nums[i];
maxLeftBorderSum = Math.max(maxLeftBorderSum,sum);
}
// 計算從mid+1 到right的最大子序和,sum清零
sum = 0;
for (int i = mid + 1; i <= right; ++i) {
sum+=nums[i];
maxRightBorderSum = Math.max(maxRightBorderSum,sum);
}
return maxLeftBorderSum + maxRightBorderSum;
}
}
時間複雜度:O(n),空間複雜度:O(logn)
執行用時:3ms
「解法2」相較於「解法1」來說,時間複雜度相同,但是因為使用了遞迴,執行的時間略長,空間複雜度也不如解法1優秀,而且難以理解。那麼這種方法存在的意義是什麼呢?
對於這道題而言,確實是如此的。但是仔細觀察「解法2」,它不僅可以解決區間 [0, n - 1],還可以用於解決任意的子區間 [l, r] 的問題。如果我們把 [0, n - 1]分治下去出現的所有子區間的資訊都用堆式儲存的方式記憶化下來,即建成一顆真正的樹之後,我們就可以在 O(logn) 的時間內求到任意區間內的答案,我們甚至可以修改序列中的值,做一些簡單的維護,之後仍然可以在 O(logn) 的時間內求到任意區間內的答案,對於大規模查詢的情況下,這種方法的優勢便體現了出來。這棵樹就是——線段樹。