题目描述 给定一个数组 array[1, 4, -5, 9, 8, 3, -6],在这个数字中有多个子数组,子数组和最大的应该是:[9,
8, 3],输出20,再比如数组为[1, -2, 3, 10, -4, 7, 2, -5],和最大的子数组为[3, 10, -4, 7,
2],输出18。
一、暴力解法(时间复杂度O(n^3),空间复杂度O(1))
思路分析
1、可以将给定数组的的所有子数组枚举出来,然后找到子数组和最大的情况,具体来说就是:遍历以A[i]为起点,A[j]为终点的子数组,比较各个子数组的大小,找到最大连续子数组;
最最直观也是最野蛮的办法便是,三个for循环三层遍历,求出数组中每一个子数组的和,最终求出这些子数组的最大的一个值。
记Sum[i, …, j]为数组A中第i个元素到第j个元素的和(其中0 <= i <= j < n),遍历所有可能的Sum[i, …, j],那么时间复杂度为O(N^3):
2、这种方法只是一般思路,时间复杂度太高,为:O(n^3),不应该选择这样的方法。
public int maxSubArray(int[] array) {
if (array == null || array.length <= 0){
return 0;
}
int max= array[0]; // 此处初始值不能是0
for(int i = 0;i < array.length; i++) {
for(int j = i;j < array.length; j++) {
int sum = 0;
for(int k = i; k <= j; k++) {
sum += array[k];
}
if(sum > max) {
max = sum;
}
}
}
return max;
}
二、在上面的代码基础上加以改进,使时间复杂度变为O(n^2),空间复杂度O(1);先从第一个元素开始向后累加,每次累加后与之前的和比较,保留最大值,再从第二个元素开始向后累加…
public int maxSubArray(int[] array) {
if (array == null || array.length <= 0){
return 0;
}
int max= array[0];
for(int i = 0;i < array.length; i++) {
int sum = 0;
for(int j = i;j < array.length; j++) {
sum += array[j];
if(sum > max) {
max = sum;
}
}
}
return max;
}
三、动态规划思想(时间复杂度变为O(n),空间复杂度O(n))
按照常规思路,一般这样定义dp数组:array[0…i]中的最大子数组和为dp[i].如果这样定义的话,整个array数组最大子数组和就是dp[n-1].如何找状态转移方程呢?按照数学归纳法,假设我们知道了 dp[i-1],如何推导出 dp[i] 呢?
实际上是不行的,因为子数组一定是连续的,按照我们当前 dp 数组定义,并不能保证 array[0…i] 中的最大子数组与 array[i+1] 是相邻的,也就没办法从 dp[i] 推导出 dp[i+1]。
所以说我们这样定义 dp 数组是不正确的,无法得到合适的状态转移方程。对于这类子数组问题,我们就要重新定义 dp 数组的含义:
以 array[i] 为结尾的「最大子数组和」为 dp[i]。
这种定义之下,想得到整个 array数组的「最大子数组和」,不能直接返回 dp[n-1],而需要遍历整个 dp 数组:
int res = Integer.MIN_VALUE;
for (int i = 0; i < n; i++) {
res = Math.max(res, dp[i]);
}
return res;
使用数学归纳法来找状态转移关系:假设我们已经算出了 dp[i-1],如何推导出 dp[i] 呢?
可以做到,dp[i] 有两种「选择」,要么与前面的相邻子数组连接,形成一个和更大的子数组;要么不与前面的子数组连接,自成一派,自己作为一个子数组。
// 要么自成一派,要么和前面的子数组合并
dp[i] = Math.max(array[i], array[i] + dp[i - 1]);
状态方程 : max( dp[ i ] ) = getMaxSum( max( dp[ i -1 ] ) + arr[ i ] ,arr[ i ] )
动态规划解析:
状态定义: 设动态规划列表 dp ,dp[i] 代表以元素 array[i] 为结尾的连续子数组最大和。
转移方程: 若 dp[i-1] ≤0 ,说明 dp[i−1] 对 dp[i] 产生负贡献,即 dp[i−1]+array[i] 还不如 array[i] 本身大。
当 dp[i - 1] > 0时:执行 dp[i] = dp[i-1] + array[i];
当 dp[i−1]≤0 时:执行 dp[i] = array[i];
初始状态: dp[0] = array[0],即以 array[0]结尾的连续子数组最大和为 array[0] 。
返回值: 返回 dp 列表中的最大值,代表全局最大值。
class Solution {
public int maxSubArray(int[] array) {
if (array == null || array.length <= 0){
return 0;
}
int max = array[0]; // 最后要返回的最大子数组的和
int[] dp = new int[array.length];
dp[0] = array[0];
for(int i = 1;i < array.length; i++) {
dp[i] = getMax(dp[i-1]+ array[i], array[i]);
if(dp[i] > max) {
max = dp[i];
}
}
return max;
}
int getMax(int a, int b) {
if(a>=b) {
return a;
}else{
return b;
}
}
}
四、动态规划思想(时间复杂度变为O(n),空间复杂度O(1)),此方法是将dp数组的空间复杂度降低了,sum其实就是dp[i],只是不将其存到额外的dp数组中。
int getMax(int a, int b) {
if(a>=b) {
return a;
}else{
return b;
}
}
public int maxSubArray(int[] array) {
if (array == null || array.length <= 0){
return 0;
}
int max = array[0]; // 最后要返回的最大子数组的和
int sum = array[0]; // 当前最大子数组的和
for(int i = 1;i < array.length; i++) {
sum = getMax(sum+ array[i], array[i]);
if(sum > max) {
max = sum;
}
}
return max;
}
五、(可输出子串)对于数组array,从array[0]开始逐个进行相加,如果前i-1个元素的最大子数组的和sum大于0的话,就对前i个元素的子数组和最大和sum有正相关性,否则前i-1个元素的最大子数组的和sum小于0的话,就对前i个元素的子数组和最大和sum有负相关性,将前i个元素的最大子数组的和sum舍弃,重新开始从第i个元素开始计算子数组的和sum,将前i个的子数组的最大和sum与最大值max比较,并不停地更替最大值max。
public int maxSubArray(int[] array) {
int max = array[0];
int sum = array[0];
int start = 0;
int end = 0; //标志开始和结束位置
for(int i = 1; i < array.length; i++) {
if(sum < 0) {
sum = array[i];
start = i;
}else{
sum += array[i];
}
if(sum > max) {
max = sum;
end = i;
}
}
return max;
}