《编程之美》解题报告:2.14 求数组的子数组之和的最大值

引言

?
?

本文用了五种方法来求解这道题,从最初的n^3的复杂度到后面的n的复杂度,分别用到了递推公式、分而治之以及动态规划的方法去一步步降低算法的复杂度。

?
?

解题报告

?
?

首先我们很容易想到的一个解法就是三层遍历,首先子数组必定是连续的一串值,相当于从原数组array的下标范围0~n-1中选出i和j,去算arra[i]~array[j]的和,于是我们可以得到最初的第一个解法

?
?

public static int sol1(int[] array) {

int lenght = array.length;

int result = Integer.MIN_VALUE;

int tempSum = 0;

for (int i = 0; i < lenght; i++) {

for (int j = i; j < lenght; j++) {

tempSum = 0;

for (int k = i; k <= j; k++) {

tempSum += array[k];

}

if (tempSum > result) {

result = tempSum;

}

}

}

return result;

}

?
?

接下来,由于array[i]~array[j]的和可以由array[i]~array[j-1]的和再与array[j]相加得到,所以我们可以将上面的代码减少一层循环,从而将复杂度降低到n^2

?
?

public static int sol2(int[] array) {

int lenght = array.length;

int result = Integer.MIN_VALUE;

for (int i = 0; i < lenght; i++) {

int tempSum = 0;

for (int j = i; j < lenght; j++) {

tempSum += array[j];

if (tempSum > result) {

result = tempSum;

}

}

}

return result;

}

?
?

现在已经降低到n^2了,但是我们并没有用到分而治之的方法,一般这种二分法可以将复杂度再一次降低到nlogn,那么如何去使用呢。

?
?

考虑将所给数组array(array[0]~array[n-1])分为长度相等的两段数组(array[0],...,array[n/2-1])和(array[n/2],...,array[n-1])

?
?

然后分别求出这两段数组各自最大子段和,得到 MaxLeftBorderSum和MaxRightBorderSum

?
?

则原数组(array[0],...,array[n-1])的最大子段和分为以下三种情况:

?
?

a.(array[0],...,array[n-1])的最大子段和与(array[0],...,array[n/2-1])的最大子段和相同;

b.(array[0],...,array[n-1])的最大子段和与(array[n/2],...,array[n-1])的最大子段和相同;

c.(array[0],...,array[n-1])的最大子段跨过其中间两个元素array[n/2-1]到array[n/2].

?
?

对应a和b两个问题是规模减半的两个相同的子问题,可以用递归求得。

对于c,需要找到以array[n/2-1]结尾的和最大的一段数组和S1=(array[i],...,array[n/2-1])和以array[n/2]开始和最大的一段和S2=(array[n/2],...,array[j]),那么第三种情况的最大值为S1+S2。

?
?

public static int sol3(int[] array, int left, int right) {

if (left == right) {// 只有一个元素

if (array[left] > 0)

return array[left];

else

return 0;

}

int start = left;

int mid = (left + right) / 2;

int end = right;

int MaxLeftBorderSum, MaxRightBorderSum; // 从中间分别到左右两侧的最大连续子序列值,对应c。

int LeftBorderSum, RightBorderSum;

int MaxLeftSum = sol3(array, start, mid);

int MaxRightSum = sol3(array, mid + 1, end);

MaxLeftBorderSum = 0;

LeftBorderSum = 0;

for (int i = mid; i >= left; i--) {

LeftBorderSum += array[i];

if (LeftBorderSum > MaxLeftBorderSum)

MaxLeftBorderSum = LeftBorderSum;

}

MaxRightBorderSum = 0;

RightBorderSum = 0;

for (int i = mid + 1; i <= right; i++) {

RightBorderSum += array[i];

if (RightBorderSum > MaxRightBorderSum)

MaxRightBorderSum = RightBorderSum;

}

int max1 = MaxLeftSum > MaxRightSum ? MaxLeftSum : MaxRightSum;

int max2 = MaxLeftBorderSum + MaxRightBorderSum;

return max1 > max2 ? max1 : max2;

}

?
?

以上已经利用分而治之的方法将复杂度从n^2降低到了nlogn,考虑到还有一种降低复杂度的方法那就是动态规划,所以我们尝试用动态规划的思想去进一步降低复杂度

?
?

动态规划的思想是将一个大问题(N个元素数组)转换为一个较小的问题(n-1个元素数组)。

?
?

我们假设result[0]为已经找到数组[0,1,...n-1]中子数组和最大的,即保存当前找到的最大子数组。sum[i]为包含第i个元素且和最大的连续子数组。对于数组中的第i+1个元素有两种选择:

?
?

a.作为新子数组的第一个元素

b.放入前面已经找到的最大的子数组sum[i-1]中。

?
?

public static int sol4(int[] array) {

int n = array.length;

int[] sum = new int[n];

int[] result = new int[n];

sum[0] = array[0];

result[0] = array[0];

for (int i = 1; i < n; i++) {

sum[i] = max(array[i], array[i] + sum[i - 1]);

// 若A[i]>A[i]+sum[i-1],则作为新子数组的第一个元素

// 否则放入前面已经找到的最大的子数组sum[i-1]中

result[i] = max(sum[i], result[i - 1]);

}

return result[n - 1];

}

?
?

以上的代码其实sum数组和result数组可以只用一个变量就可以了,不用真的用到一个数组,那么将这一点简化,可以得到简化版的最终方法,复杂度为n

?
?

static int sol5(int[] array, int n) {

int sum = array[0];

int result = array[0];

for (int i = 1; i < n; i++) {

sum = max(array[i], array[i] + sum);

result = max(sum, result);

}

return result;

}

时间: 2024-10-11 23:17:40

《编程之美》解题报告:2.14 求数组的子数组之和的最大值的相关文章

编程之美之2.14 求数组的子数组之和的最大值

[题目] 一个有N个整数元素的一维数组(A[0],A[1],A[2],...A[n-1]),这个数组中当然有很多子数组,那么子数组之和的最大值是多少? 该子数组是连续的. 我们先来明确一下题意: (1)子数组意味着是连续的. (2)题目只需要求和,并不需要返回子数组的具体位置. (3)数组的元素是整数,所以数组可能包含正整数,负整数或者零. 举几个例子: 数组:[1,-2,3,5,-3,2]返回8 数组:[0,-2,3,5,-1,2]返回9 数组:[-9,-2,-3,-5,-3]返回8 [解法一

[编程之美] 2.14 求数组的子数组之和的最大值

问题描述:给定一个包含N个整数的数组,求数组的子数组之和的最大值. 这是递归和贪心策略的一个经典问题.现在,对这个问题进行一下总结. 1 明确题意 题目中的子数组要求是连续的,也就是数组中的某个连续部分. 如果数组中都是正整数,直接相加就行.因此,主要是要考虑负数的情况. 2 直接求所有的子数组和 最简单且容易理解的解法是求出所有的子数组和,然后保存最大的和. int MaxSum(int *A, int n) { int maximum = -INF; int sum = 0; int i =

编程之美2.14 求数组的子数组之和的最大值

问题描述: 一个有N个整数元素的一维数组(A[0], A[1], A[2],...,A[n-1]),这个数组当然有很多子数组,那么子数组之和的最大值是什么呢? 解法: 1. 暴力解法-------O(N^3) 2. 改进版暴力解法-------O(N^2) *3. 分治算法-------O(NlogN)(暂时未去实现) 4. 数组间关系法-------O(N) 具体思路和代码: 1.暴力解法 思路:Sum[i,...,j]为数组第i个元素到第j个元素的和,遍历所有可能的Sum[i,...,j].

2.14 求数组的子数组之和的最大值

题目:给定一个一维数组,求这个数组的子数组之和的最大值. 最佳方法:动态规划! 一. 可以将一个大问题(N个元素数组)转化为一个较小的问题(N-1个元素数组).. 假设已经知道(A[1],...A[n-1])中最大的子数组的和为:All[1] 并且已经知道(A[1],...A[n-1])中包括A[1]的子数组的最大和为start[1] 所以最终的解All[0] = max(A[0], A[0]+start[1], All[1]) 所以通过DP来求解! 代码如下: #include <iostre

编程之美 2.14求数组的子数组之和的最大值

对于一个有N个元素的数组,a[0]~a[n-1],求子数组最大值. 如:数组A[] = [−2, 1, −3, 4, −1, 2, 1, −5, 4],则连续的子序列[4,−1,2,1]有最大的和6. 方法一:暴力 循环遍历,输出所有,判断最大的和 1 #include"iostream" 2 #define MAX 1001 3 using namespace std; 4 5 int main(){ 6 int n, a[MAX], sum , maxsum ; 7 8 cin &

【编程之美】2.14 求数组的子数组之和的最大值

做过的,就说下思路. 用Sum记录A[0...N-1]中 以第i个元素结尾的子数组中的最大和, 若以第i-1个元素结尾的子数组小于0,那么以第i个元素结尾的子数组中的最大和就是 A[i]本身 否则是A[i] + Sum(i-1的) 总结起来就是 Sum = (Sum > 0) ? A[i] + Sum : A[i]: 再记录下所有出现过的Sum中最大的值就可以了. #include <stdio.h> int getMaxSubArraySum(int * a, int alen) {

第2章 数字之魅——求数组的子数组之和的最大值

求数组的子数组之和的最大值 问题描述 分析与解法 [解法一] 具体代码如下: 1 package chapter2shuzizhimei.maxsumsubarray; 2 /** 3 * 求数组的子数组之和的最大值 4 * [解法一] 5 * @author DELL 6 * 7 */ 8 public class MaxSumSubArray1 { 9 //求数组的子数组之和的最大值 10 public static double maxSum(double a[]){ 11 double

求数组的子数组之和的最大值?

自己写的代码考虑未周全,引入了额外的空间复杂度: //求数组的子数组之和的最大值 #include <iostream> #define N 12 using namespace std; int main() { //int a[]={-5,2,3,-3,-2,3,1,-5}; //int a[]={-5,2,0,3,-2,3,4,5}; int a[]={1,-2,3,10,-4,7,2,-5}; int flag,max,i,j=0; int sum[N]={0}; //(1)记录子数组

【编程之美】求数组的子数组之和的最大值

一个有N个整数元素的一维数组A[0],A[1],......,A[n-1],这个数组当然有很多子数组,那么子数组的最大值是什么呢? 分析与解法 我们先明确题意: 1. 题目说的子数组,是连续的: 2. 题目只需要求和,并不需要返回子数组的具体位置: 3. 数组中的元素是整数,所以数组可能包含有正整数.零.负整数: 4. 子数组不为空. 解法一:枚举 最简单的办法就是枚举所有的i和j,计算sum[i..j] = A[i]+A[i+1]+...+A[j],遍历所有可能的sum[i..j],找到最大值