【Java】如何求最大子陣列之和
問題描述
一個有n個元素的陣列,這n個元素可以是正數也可以是負數,陣列中連續的一個或多個元素可以組成一個連續的子陣列,一個數組可能有多個這種連續的子陣列,求子陣列和的最大值。
輸入示例
輸入陣列{1,-2,4,8,-4,7,-1,-5}
輸出示例
最大值:15
其最大和的子陣列為{4,8,-4,7}
方法一:“蠻力”法
最簡單也是最容易想到的方法就是找出所有子陣列,然後求出子陣列的和,在所有子陣列的和中取最大值。程式碼如下:
package xiaozhaobishi;
import java.util.Scanner;
/**
* @author wilson_m
* @date 2018年8月19日
* @Description:
* @version 1.0.0
*/
public class maxSubArray {
public static int maxSubArrayMethodOne(int arr[]){
int n = arr.length;
int ThisSum=0,MaxSum=0,i,j,k;
for(i=0;i<n;i++){
for(j=i;j<n;j++){
ThisSum=0 ; //重新累加子陣列
for(k=i;k<j;k++){
ThisSum+=arr[k];
}
if(ThisSum>MaxSum){
MaxSum=ThisSum;
}
}
}
return MaxSum;
}
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
int number=Integer.parseInt(sc.next());
//number表示陣列的長度
int[] num=new int[number];
//對陣列元素迴圈賦值
for(int i=0;i<number;i++){
num[i]=(int)sc.nextInt();
}
System.out.println(maxSubArrayMethodOne(num));
}
}
程式執行結果:
輸入:
8 1 -2 4 8 -4 7 -1 -5
輸出:
15
以上這個演算法的時間複雜度為O(n^3),顯然效率太低,通過對該演算法進行分析發現,許多子陣列都重複計算了,鑑於此,下面給出一種優化的方法。
方法二:重複利用已經計算的子陣列和
例如Sum[i,j]=Sum[i,j-1]+arr[j],採用這種方法可以省去計算Sum[i,j-1]的時間,因此可以提高程式的效率。示例如下:
package xiaozhaobishi;
import java.util.Scanner;
/**
* @author wilson_m
* @date 2018年8月19日 、
* 求最大子陣列之和
* @Description:
* @version 1.0.0
*/
public class maxSubArray {
public static int maxSubArrayMethodTwo(int arr[]){
int size =arr.length;
int maxSum=Integer.MIN_VALUE;
for(int i=0;i<size;i++){
int sum=0;
for(int j=i;j<size;j++){
sum+=arr[j];
if(sum>maxSum){
maxSum=sum;
}
}
}
return maxSum;
}
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
int number=Integer.parseInt(sc.next());
//number表示陣列的長度
int[] num=new int[number];
//對陣列元素迴圈賦值
for(int i=0;i<number;i++){
num[i]=(int)sc.nextInt();
}
System.out.println(maxSubArrayMethodTwo(num));
}
}
程式執行結果:
輸入:
3 -4 6 -8 4 -3 5
輸出:
6
以上這個演算法的時間複雜度為O(n^2)
方法三:動態規劃方法
可以採用動態規劃的方法來降低演算法的時間複雜度,實現思路如下:
首先可以根據陣列的最後一個元素[n-1]與最大子陣列的關係分為以下3種情況:
1)最大子陣列的包含arr[n-1],即以arr[n-1]結尾。
2)arr[n-1]單獨構成最大子陣列。
3)最大子陣列不包含arr[n-1],那麼求arr[1,…,n-1]的最大子陣列可以轉換為求arr[1,…,n-2]的最大子陣列。
通過以上分析可以得出如下結論:假設已經計算出(arr[0],…,arr[i-1])最大的一段陣列和為All[i-1],同時也計算出(arr[0],…,arr[i-1])中包含arr[i-1]的最大的一段陣列和為End[i-1],則可以得出如下關係:All[i-1]=max{arr[i-1],End[i-1],All[i-2]}。利用這個公式和動態規劃的思想可以得到如下程式碼:
package xiaozhaobishi;
import java.util.Scanner;
/**
* @author wilson_m
* @date 2018年8月19日
* 求最大子陣列之和
* @Description:
* @version 1.0.0
*/
public class maxSubArray {
public static int max(int m,int n){
return m>n ? m :n;
}
public static int maxSubArrayMethodThree(int arr[]){
int n=arr.length;
int End[]=new int[n];
int All[]=new int[n];
End[n-1]=arr[n-1];
All[n-1]=arr[n-1];
End[0]=All[0]=arr[0];
for(int i=1;i<n;i++){
End[i]=max(End[i-1]+arr[i],arr[i]);
All[i]=max(End[i],All[i-1]);
}
return All[n-1];
}
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
int number=Integer.parseInt(sc.next());
//number表示陣列的長度
int[] num=new int[number];
//對陣列元素迴圈賦值
for(int i=0;i<number;i++){
num[i]=(int)sc.nextInt();
}
System.out.println(maxSubArrayMethodThree(num));
}
}
程式執行結果:
輸入:
3 -4 6 -8 4 -3 5
輸出:
6
與前幾種方法相比,這種方法的時間複雜度為O(n),顯然效率更高,但是由於在計算的過程中額外申請了兩個陣列空間,因此該演算法的空間複雜度也為O(n)。
方法四:優化的動態方法
方法三中每次只用到End[i-1]與All[i-1],而不是整個陣列中的值,因此可以定義兩個變數來儲存End[i-1]與All[i-1]的值,並且可以反覆利用,這樣就可以在保證時間複雜度為O(n)的同時降低空間複雜度。例項如下:
package xiaozhaobishi;
import java.util.Scanner;
/**
* @author wilson_m
* @date 2018年8月19日 、
* 求最大子陣列之和
* @Description:
* @version 1.0.0
*/
public class maxSubArray {
public static int max(int m,int n){
return m>n ? m :n;
}
public static int maxSubArrayMethodFour(int arr[]){
int n=arr.length;
int nAll=arr[0]; //有n個數字陣列的最大子陣列和
int nEnd=arr[0]; //有那個數字陣列包含最後一個元素的子陣列的最大和
for(int i=1;i<n;i++){
nEnd=max(nEnd+arr[i],arr[i]);
nAll=max(nEnd,nAll);
}
return nAll;
}
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
int number=Integer.parseInt(sc.next());
//number表示陣列的長度
int[] num=new int[number];
//對陣列元素迴圈賦值
for(int i=0;i<number;i++){
num[i]=(int)sc.nextInt();
}
System.out.println(maxSubArrayMethodFour(num));
}
}
程式執行結果:
輸入:
8 1 -2 4 8 -4 7 -1 -5
輸出:
15
在知道子陣列的最大和之後,如何才能確定最大子陣列的位置呢?為了得到最大子陣列的位置,首先介紹另外一種計算最大子陣列和的方法。在方法三中,通過對公式End[i]=max(End[i-1]+arr[i],arr[i])的分析可以看出,當End[i-1]<0時,End[i]=arr[i],其中End[i]表示包含arr[i]的子陣列和,如果某一個值使得End[i-1]<0,那麼就從arr[i]重新開始。示例如下:
package xiaozhaobishi;
import java.util.Scanner;
/**
* @author wilson_m
* @date 2018年8月19日
* @Description:
* @version 1.0.0
*/
public class maxSubArrayMethodFive {
private static int begin=0; //記錄最大子陣列的起始位置
private static int end=0; //記錄最大子陣列的結束位置
public static int maxSubArray(int arr[]){
int maxSum=Integer.MIN_VALUE; //子陣列最大值
int nSum=0; //包含子陣列最後一位的最大值
int nStart=0;
for(int i=0;i<arr.length;i++){
if(nSum<0){
nSum=arr[i];
nStart=i;
}
else{
nSum+=arr[i];
}
if(nSum>maxSum){
maxSum=nSum;
begin=nStart;
end=i;
}
}
return maxSum;
}
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
int number=Integer.parseInt(sc.next());
//number表示陣列的長度
int[] num=new int[number];
//對陣列元素迴圈賦值
for(int i=0;i<number;i++){
num[i]=(int)sc.nextInt();
}
System.out.println(maxSubArray(num));
System.out.println("begin="+begin+";end="+end);
}
}
程式執行結果:
8 1 -2 4 8 -4 7 -1 -5
15
begin=2;end=5