1. 53. 最大子序和
Difficulty: 簡單
給定一個整數數組 nums
,找到一個具有最大和的連續子數組(子數組最少包含一個元素),返回其最大和。
示例:
輸入: [-2,1,-3,4,-1,2,1,-5,4],
輸出: 6
解釋: 連續子數組 [4,-1,2,1] 的和最大,爲 6。
進階:
- 如果你已經實現複雜度爲 O(n) 的解法,嘗試使用更爲精妙的分治法求解。
- 可以化成’如何求解股票利益最大化’的題(算法導論)
2. Solution
2.1. 分治法求解
- 有最優子結構: 原問題的最優解包含縮小規模子問題的最優解
class Solution {
public int maxSubArray(int[] nums) {
// 最優子串可能在左串 可能在右串 可能是包含中間元素的中間串
return recur(nums,0,nums.length-1);
}
// 遞歸 分治法
private int recur(int[] nums, int l, int r) {
if(l==r) return nums[l];// 只有一個元素的時候
int mid=(l+r)/2;
// 對左中右取最大
int leftSum=recur(nums,l,mid);
int rightSum=recur(nums,mid+1,r);
// cross的計算不是原問題規模更小的問題 是合併的一部分
int crossSum=midSum(nums, l, r, mid);
int res=Math.max(leftSum, Math.max(rightSum, crossSum));
return res;
}
// 求中間子串: 這個求和不是原問題的子問題(必須 跨越中點) 所以不用recur()計算
private int midSum(int[] nums, int l, int r,int mid) {
if(l==r) return nums[l];// 只有一個元素
int sumTmp=0,leftSum=Integer.MIN_VALUE;
int rightSum=leftSum;
for(int i=mid;i!=-1;i--) {
sumTmp+=nums[i];
leftSum=Math.max(sumTmp, leftSum);
}
sumTmp=0;
for(int i=mid+1;i!=nums.length;i++) {
sumTmp+=nums[i];
rightSum=Math.max(sumTmp, rightSum);
}
return leftSum+rightSum;
}
}
動態優化解法
class Solution {
private int[] iToLeft;
private int[] nums;
public int maxSubArray(int[] nums) {
if(nums==null||nums.length==0) return 0;
// 記錄以i爲右子序列端點的最大累和
this.nums=nums;
iToLeft=new int[nums.length];
iToLeft[0]=nums[0];
for(int i=1;i!=iToLeft.length;i++) {
// 要麼包含以i-1位右端點的最大累和,要麼從i重新開始
iToLeft[i]=Math.max(iToLeft[i-1]+nums[i],nums[i]);
}
//動態優化: 遞歸縮減範圍
int res = recur(nums.length-1);
return res;
}
private int recur(int n) {
if(n==0) return iToLeft[0];
int res = Math.max(recur(n-1), iToLeft[n]);
return res;
}
}
2.2. 優化空間複雜度爲O(1)
class Solution {
public int maxSubArray(int[] nums) {
int len = nums.length;
int currSum = nums[0]; // 每一步的當前最優解
int maxSum = nums[0];
for (int i = 1; i < len; ++i) {
//記錄右靠區間的最大子區間值
currSum = Math.max(nums[i], currSum + nums[i]);
// 將當前最優子區間和i之前的最大子區間和比較
maxSum = Math.max(maxSum, currSum);
}
return maxSum;
}
}