最大子段和問題
給定由n個整數組成的序列(),求該序列形如 的子段和的最大值,當所有整數均爲負整數時,其最大子段和爲0。
問題分析
簡單的說就是求一個序列中截取一段連續的序列,要求截取的序列的和爲最大值。
舉例:
- 如序列爲(-5,8,12,-6,8,-14)的最大連續的序列爲(8,12,-6,8)其和爲22
- 如序列爲(-5,8,12,-6,8,-14,-15,5,20)的最大連續的序列爲(5,20)其和爲25
- 如序列爲(-5,-8,-6,-14)無最大連續的序列,最大子段和爲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=()和連續序列B=()。則最大子段和有三種情況:
- A中的最大子段和爲S的最大子段和
- B中的最大子段和爲S的最大子段和
- S的最大子段和爲A和B中交界部分,應爲,且
難點:求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 =,則考慮,若則maxSum =,若則maxSum =。故得到遞推式子爲:maxSum = max{,}。需要注意的是若爲負數,則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是博主誤打誤撞想出來的,如有雷同,純屬巧合。。。
這個idea的時間複雜度和動態規劃一樣都是O(n),空間複雜度也爲O(n)
想法:將所給序列S=()變成一個爲(正,負,正,負…)或者(負,正,負,正,…)的序列。比如序列(-5,8,12,-6,8,-14,-15,5,20)就應該變爲(-5,20,-6,8,-15,25)。然後就只考慮正數了,maxSum = max,這裏的肯定爲負數,肯定爲正數。
難點:遞推式不好理解,建議參考代碼理解。
代碼如下:
//自己的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版》