1. 程式人生 > >【Java】如何求最大子陣列之和

【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