给定由n个整数组成的序列( a 1 , a 2 , . . . , a n a_1,a_2,...,a_n a1,a2,...,an),求该序列形如 ∑ k = i j a k \sum\limits_{k=i}^j a_k k=i∑jak的子段和的最大值,当所有整数均为负整数时,其最大子段和为0。
简单的说就是求一个序列中截取一段连续的序列,要求截取的序列的和为最大值。
举例:
思路:找出序列的所有连续的序列,然后找出最大子段和
难点:这个区别于全排列,连续的序列是连续的(有点废话。。),所以通过两个for循环扫描即可,一个指向序列的头,一个指向序列的尾部扫秒即可。
代码如下:
//蛮力法(很暴力,我喜欢)
public static int maxSubseqSumByBF(int arr[]){
int length = arr.length;
int maxSum = 0; //用来记录最大值
for (int i = 0; i < length; i++) { //这个循环扫描出所有连续序列
int nowSum = 0;
for (int j = i; j < length; j++) { //这个循环扫描出所有的以i开头的连续序列
nowSum += arr[j];
if(nowSum > maxSum){
maxSum = nowSum;
}
}
}
return maxSum;
}
思路:将序列S=( a 1 , a 2 , . . . , a n a_1,a_2,...,a_n a1,a2,...,an)划分成两个长度相同的连续序列A=( a 1 , a 2 , . . . , a n / 2 a_1,a_2,...,a_{n/2} a1,a2,...,an/2)和连续序列B=( a n / 2 + 1 , . . . , a n a_{n/2+1},...,a_n an/2+1,...,an)。则最大子段和有三种情况:
难点:求A和B中的交界部分的最大子段和。从n/2处分别往左右两端找出最大子段和,然后合并左右的最大子段和即为交界部分的最大子段和。
代码如下:
//分治法(递归方法求解)
public static int maxSubseqSum_ByDC(int arr[],int lo,int hi){
int center = lo + (hi-lo)/2;
int maxLeftSum,maxRightSum; //分别表示左右最大字段和
int borLeftSum,borRightSum; //分别表示在center左右的最大字段和
if(lo == hi){
if(arr[lo] > 0)
return arr[lo];
else
return 0;
}
maxLeftSum = maxSubseqSum_ByDC(arr,lo,center);
maxRightSum = maxSubseqSum_ByDC(arr,center+1,hi);
//求解交界部分的最大值,并与左右部分比较得出最大子段和
borLeftSum = 0;
borRightSum = 0;
int tempSum = 0;
for (int i = center; i <= hi; i++) { //往右找出最大子段和
tempSum += arr[i];
if(tempSum > borRightSum){
borRightSum = tempSum;
}
}
tempSum = 0;
for (int i = center; i >= lo; i--) { //往左找出最大子段和
tempSum += arr[i];
if(tempSum > borLeftSum){
borLeftSum = tempSum;
}
}
//比较左,右,交界处的子段和得出最大字段和
int maxSum = Math.max(maxLeftSum,maxRightSum);
maxSum = Math.max(maxSum,borLeftSum+borRightSum-arr[center]);
return maxSum;
}
思路:若最大子段和为:maxSum = ∑ k = i j a k \sum\limits_{k=i}^j a_k k=i∑jak,则考虑 a j a_j aj,若 a j > 0 a_j\gt0 aj>0则maxSum = ∑ k = i j − 1 a k + a j \sum\limits_{k=i}^{j-1} a_k + a_j k=i∑j−1ak+aj,若 a j < 0 a_j\lt0 aj<0则maxSum = ∑ k = i j − 1 a k \sum\limits_{k=i}^{j-1} a_k k=i∑j−1ak。故得到递推式子为:maxSum = max{ ∑ k = i j − 1 a k + a j \sum\limits_{k=i}^{j-1} a_k + a_j k=i∑j−1ak+aj, ∑ k = i j − 1 a k \sum\limits_{k=i}^{j-1} a_k k=i∑j−1ak}。需要注意的是若为负数,则maxSum=0。
难点:代码没啥难点,难就在不好想出递推式。。。先看代码有助于理解思路
代码如下:
//动态规划
public static int maxSubseqSumByDP(int arr[]){
int length = arr.length;
int MaxSum = 0,NowSum = 0;
for (int i = 0; i < length; i++) {
NowSum += arr[i];
if(NowSum < 0){
NowSum = 0;
}
MaxSum = Math.max(NowSum,MaxSum);
}
return MaxSum;
}
这个idea是博主误打误撞想出来的,如有雷同,纯属巧合。。。
这个idea的时间复杂度和动态规划一样都是O(n),空间复杂度也为O(n)
想法:将所给序列S=( a 1 , a 2 , . . . , a n a_1,a_2,...,a_n a1,a2,...,an)变成一个为(正,负,正,负…)或者(负,正,负,正,…)的序列。比如序列(-5,8,12,-6,8,-14,-15,5,20)就应该变为(-5,20,-6,8,-15,25)。然后就只考虑正数了,maxSum = max ( ∑ k = i j − 2 a k + a j − 1 + a j , ∑ k = i j − 2 a k ) (\sum\limits_{k=i}^{j-2} a_k + a_{j-1}+a_j,\sum\limits_{k=i}^{j-2} a_k) (k=i∑j−2ak+aj−1+aj,k=i∑j−2ak),这里的 a j − 1 a_{j-1} aj−1肯定为负数, a j a_j aj肯定为正数。
难点:递推式不好理解,建议参考代码理解。
代码如下:
//自己的idea
public static int maxSubseqSumByme(int[] arr){
int len = arr.length;
int[] tempValue = new int[len]; //记录暂时的值
ArrayList<Integer> alist = new ArrayList<>(); //存储数据为(正,负,正,负...)或(负,正,负...)
tempValue[0] = arr[0]; //先考虑第一个,为了不越界
//找出序列中连续存在的正数总和和负数总和
for (int i = 1; i < len; i++) {
if(arr[i-1] < 0 && arr[i] < 0){
tempValue[i] = arr[i] + tempValue[i-1];
}else if(arr[i-1] > 0 && arr[i] > 0){
tempValue[i] = arr[i] + tempValue[i-1];
}else{
tempValue[i] = arr[i];
alist.add(tempValue[i-1]); //这里为什么存储的i-1需要理解一下
}
}
alist.add(tempValue[len-1]); //将最后一个存储进去
//求解最大子段和
int maxValue = alist.get(0)>0?alist.get(0):0; //先考虑第一个,为了不越界
for (int i = 1; i < alist.size(); i++) {
if(alist.get(i)>0){
maxValue = Math.max(alist.get(i),maxValue+alist.get(i-1)+alist.get(i));
}
}
return maxValue;
}
直接copy即可
import java.util.ArrayList;
public class MaxSubseqSum {
public static void main(String[] args) {
int[] arr = {10,-5,8,12,-6,8,-14,-15,5,20};
System.out.println("数组为:");
for (int i = 0; i < arr.length; i++) System.out.print(arr[i]+" ");
System.out.println();
System.out.println("蛮力法求解最大字段和为:");
System.out.println(maxSubseqSumByBF(arr));
System.out.println("分治法求解最大字段和为:");
System.out.println(maxSubseqSumByDC(arr,0,arr.length-1));
System.out.println("动态规划求解最大字段和为:");
System.out.println(maxSubseqSumByDP(arr));
System.out.println("自己idea求解最大子段和为:");
System.out.println(maxSubseqSumByme(arr));
}
//蛮力法
public static int maxSubseqSumByBF(int arr[]){
int length = arr.length;
int maxSum = 0; //用来记录最大值
for (int i = 0; i < length; i++) { //这个循环扫描出所有连续序列
int nowSum = 0;
for (int j = i; j < length; j++) { //这个循环扫描出所有的以i开头的连续序列
nowSum += arr[j];
if(nowSum > maxSum){
maxSum = nowSum;
}
}
}
return maxSum;
}
//分治法(递归方法求解)
public static int maxSubseqSumByDC(int arr[],int lo,int hi){
int center = lo + (hi-lo)/2;
int maxLeftSum,maxRightSum; //分别表示左右最大字段和
int borLeftSum,borRightSum; //分别表示在center左右的最大字段和
if(lo == hi){
if(arr[lo] > 0)
return arr[lo];
else
return 0;
}
maxLeftSum = maxSubseqSumByDC(arr,lo,center);
maxRightSum = maxSubseqSumByDC(arr,center+1,hi);
//求解交界部分的最大值,并与左右部分比较得出最大子段和
borLeftSum = 0;
borRightSum = 0;
int tempSum = 0;
for (int i = center; i <= hi; i++) { //往右找出最大子段和
tempSum += arr[i];
if(tempSum > borRightSum){
borRightSum = tempSum;
}
}
tempSum = 0;
for (int i = center; i >= lo; i--) { //往左找出最大子段和
tempSum += arr[i];
if(tempSum > borLeftSum){
borLeftSum = tempSum;
}
}
//比较左,右,交界处的子段和得出最大字段和
int maxSum = Math.max(maxLeftSum,maxRightSum);
maxSum = Math.max(maxSum,borLeftSum+borRightSum-arr[center]);
return maxSum;
}
//动态规划
public static int maxSubseqSumByDP(int arr[]){
int length = arr.length;
int MaxSum = 0,NowSum = 0;
for (int i = 0; i < length; i++) {
NowSum += arr[i];
if(NowSum < 0){
NowSum = 0;
}
MaxSum = Math.max(NowSum,MaxSum);
}
return MaxSum;
}
//自己的idea
public static int maxSubseqSumByme(int[] arr){
int len = arr.length;
int[] tempValue = new int[len]; //记录暂时的值
ArrayList<Integer> alist = new ArrayList<>(); //存储数据为(正,负,正,负...)或(负,正,负...)
tempValue[0] = arr[0]; //先考虑第一个,为了不越界
//找出序列中连续存在的正数总和和负数总和
for (int i = 1; i < len; i++) {
if(arr[i-1] < 0 && arr[i] < 0){
tempValue[i] = arr[i] + tempValue[i-1];
}else if(arr[i-1] > 0 && arr[i] > 0){
tempValue[i] = arr[i] + tempValue[i-1];
}else{
tempValue[i] = arr[i];
alist.add(tempValue[i-1]); //这里为什么存储的i-1需要理解一下
}
}
alist.add(tempValue[len-1]); //将最后一个存储进去
//求解最大子段和
int maxValue = alist.get(0)>0?alist.get(0):0; //先考虑第一个,为了不越界
for (int i = 1; i < alist.size(); i++) {
if(alist.get(i)>0){
maxValue = Math.max(alist.get(i),maxValue+alist.get(i-1)+alist.get(i));
}
}
return maxValue;
}
}
参考书籍:《算法分析与设计 第2版》