我们可以从数组的最后开始往前看,对于当前数nums[i]
,以这个nums[i]
结尾的最大值一定是你前面的所有数求出一个最大的子序和(但是由于是子数组,所以必须是判断前一个数)+
我自己(nums[i]
),所以这是一个递归的过程,边界条件就是i = 0
时,最大子序和就是自己。
class Solution {
public int res;
public int maxSubArray(int[] nums) {
res = nums[0];
rec(nums, nums.length - 1);
return res;
}
public int rec(int[] nums, int index) {
if (index == 0) {
return nums[0];
} else {
int pre = rec(nums, index - 1); //先给我求出前面的最大子序和
int cur = pre > 0 ? pre + nums[index] : nums[index];
res = Math.max(cur, res);
return cur;
}
}
}
动态规划就是从小问题到大问题,递归相反的方向,我们可以正向的保存一个以每一个数结尾的最大子序和的数组,然后递推到最后一个,其中使用个max
保存最大值;
class Solution {
public int maxSubArray(int[] nums) {
int[] ends = new int[nums.length];
ends[0] = nums[0];
int max = ends[0];
for (int i = 1; i < nums.length; i++) {
ends[i] = ends[i - 1] > 0 ? ends[i - 1] + nums[i] : nums[i];
max = Math.max(max, ends[i]);
}
return max;
}
}
类似二维dp的滚动数组优化,因为每个位置只需要它前面的一个位置,所以我们只需要用一个变量保存即可。
class Solution {
public int maxSubArray(int[] nums) {
int preMax = nums[0];
int max = nums[0];
for (int i = 1; i < nums.length; i++) {
preMax = preMax > 0 ? preMax + nums[i] : nums[i];
max = Math.max(max, preMax);
}
return max;
}
}
思路:
LMax = process(arr,L,mid)
,这里的右边界不是mid-1
,而是mid
,因为边界是L==R
的时候返回arr[L]
,而且我们计算crossMax
的时候包括了arr[mid]
,就不需要再加上arr[mid]
。class Solution {
public int maxSubArray(int[] nums) {
if (nums == null || nums.length == 0)
return 0;
return rec(nums, 0, nums.length - 1);
}
//返回这个之间的最大子序和
private int rec(int[] arr, int L, int R) {
if (L == R)
return arr[L];
int mid = L + (R - L) / 2;
int LMax = rec(arr, L, mid);
int RMax = rec(arr, mid + 1, R);
int sum = 0, LSumMax = Integer.MIN_VALUE, RSumMax = Integer.MIN_VALUE;
for (int i = mid; i >= L; i--) {
sum += arr[i];
if (sum > LSumMax) {
LSumMax = sum;
}
}
sum = 0;
for (int i = mid + 1; i <= R; i++) {
sum += arr[i];
if (sum > RSumMax) {
RSumMax = sum;
}
}
int crossMax = LSumMax + RSumMax;
//compare crossMax、LMax,RMax
if (LMax >= RMax && LMax >= crossMax)
return LMax;
if (RMax >= LMax && RMax >= crossMax)
return RMax;
return crossMax;
}
}
这个和上面有点不同的是要我们求出求得最大字段和的同时,要求出左右边界,其实很简单,先记录一下右边界,然后从后往前递推左边界即可;
dp
;一维dp:
import java.io.BufferedInputStream;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner cin = new Scanner(new BufferedInputStream(System.in));
int T = cin.nextInt();
int kase = 0;
while(T-- > 0){
int n = cin.nextInt();
int[] nums = new int[n];
for(int i = 0; i < n; i++)
nums[i] = cin.nextInt();
int[] ends = new int[nums.length];
ends[0] = nums[0];
int max = ends[0],maxi = 0;
for(int i = 1; i < nums.length; i++){
ends[i] = ends[i-1] > 0 ? ends[i-1] + nums[i] : nums[i];
if(ends[i] > max){
max = ends[i];
maxi = i;
}
}
int curSum = max,L = maxi; // L 是左边界
for(int i = maxi; i >= 0; i--){
curSum -= nums[i];
if(curSum == 0){
L = Math.min(L,i);
}
}
System.out.println("Case "+(++kase)+":");
System.out.println(max+" "+(L+1)+" "+(maxi+1));
if(T != 0)System.out.println();
}
}
}
滚动优化:
import java.io.BufferedInputStream;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner cin = new Scanner(new BufferedInputStream(System.in));
int T = cin.nextInt();
int kase = 0;
while(T-- > 0){
int n = cin.nextInt();
int[] nums = new int[n];
for(int i = 0; i < n; i++)
nums[i] = cin.nextInt();
int preMax = nums[0];
int max = preMax,maxi = 0;
for(int i = 1; i < nums.length; i++){
preMax = preMax > 0 ? preMax + nums[i] : nums[i];
if(preMax > max){
max = preMax;
maxi = i;
}
}
int curSum = max,L = maxi;
for(int i = maxi; i >= 0; i--){
curSum -= nums[i];
if(curSum == 0){
L = Math.min(L,i);
}
}
System.out.println("Case "+(++kase)+":");
System.out.println(max+" "+(L+1)+" "+(maxi+1));
if(T != 0)System.out.println();
}
}
}