題目描述
給定一個整數數組,找到一個連續子數組其元素之和最大並返回
Input:nums
Output:最大連續子數組之和
三種解法,分別是動態規劃、貪心法、分治法,其中分治算法不是最優的。
1. 動態規劃
定義:以位置爲結尾的子數組最大和
需要遍歷數組一次,時間複雜度爲O(n),空間複雜度爲O(1)。缺點是直接在nums上操作,會改變原來的數組nums。
/**
* 動態規劃
* @param nums
* @return
*/
public int maxSubArrayDynamic(int[] nums) {
if(nums == null || nums.length == 0){
return 0;
}
int maxSum = nums[0];
for(int i = 1;i<nums.length;i++){
nums[i] += nums[i-1] > 0 ? nums[i-1] : 0;
maxSum = Math.max(maxSum, nums[i]);
}
return maxSum;
}
2. 貪心法
curSum用於記錄當前和(每一步都選擇最優的方案)
僅僅需要遍歷數組一次,時間複雜度爲O(n),空間複雜度爲O(1)。
/**
* 貪心
* @param nums
* @return
*/
public int maxSubArrayGreed(int[] nums) {
if(nums == null || nums.length == 0){
return 0;
}
int curSum = nums[0], maxSum = curSum;
for(int i = 1;i<nums.length;i++){
curSum = Math.max(curSum + nums[i], nums[i]);
maxSum = Math.max(maxSum, curSum);
}
return maxSum;
}
3. 分治
首先將數組劃分成左和右兩部分,具有最大和的連續子數組的位置可能有如下三種情況:
- 位於左邊的數組部分;
- 位於右邊的數組部分;
- 位於左和右交叉的部分,即最大子數組有一部分在左邊,一部分在右邊。
其中,第一種情況和第二種情況可以遞歸。第三種情況由函數crossSum暴力計算,需要遍歷數組一次,使得時間複雜度需要乘O(n)
/**
* 暴力計算包含位置p在內的最大子數組和
* @param nums
* @param left
* @param right
* @param p
* @return
*/
private int crossSum(int[] nums, int left, int right, int p){
if(left == right) return nums[left];
int leftSubsum = Integer.MIN_VALUE;
int rightSubsum = Integer.MIN_VALUE;
int curSum = 0;
for(int i = p;i >= left;i--){
curSum += nums[i];
leftSubsum = Math.max(leftSubsum, curSum);
}
curSum = 0;
for(int i = p+1;i<=right;i++){
curSum += nums[i];
rightSubsum = Math.max(rightSubsum, curSum);
}
return leftSubsum + rightSubsum;
}
private int helper(int[] nums, int left, int right){
if(left == right) return nums[left];
int p = (left + right) >>> 1;
int leftSum = helper(nums, left, p);
int rightSum = helper(nums, p+1, right);
int crossSum = crossSum(nums, left, right, p);
return Math.max(crossSum, Math.max(leftSum, rightSum));
}
/**
* 分治
* @param nums
* @return
*/
public int maxSubArrayDivide(int[] nums) {
if(nums == null || nums.length == 0){
return 0;
}
return helper(nums, 0, nums.length-1);
}
時間複雜度爲O(nlogn),helper函數多次遞歸,造成了空間複雜度爲O(logn)。