問題#
方案一#
思路##
先搜尋出所有的子序列,然后求和比較
代碼##
public static void maxSubSum1(int[] a) {
int maxSum = 0;
int startIndex = 0;
int endIndex = 0;
for (int i = 0; i < a.length; i++) {
for (int j = i; j < a.length; j++) {
int tempSum = 0;
for (int k = i; k <= j; k++) {
tempSum += a[k];
}
if (maxSum < tempSum) {
maxSum = tempSum;
startIndex = i;
endIndex = j;
}
}
}
System.out.println("this subsequence is from " + startIndex + " to " + endIndex + ", max sum is " + maxSum);
}
結果##
分析##
此方案用了三層循環,第一層循環確定子序列的起始位子(i),第二層循環確定子序列的終止位子(j)。通過第一層循環和第二層循環確定所有的子序列,然后再通過第三層循環求和。最后比較子序列的和,確定起始位子和終止位子。
此方案的時間復雜度為O(N^3 ),這完全取決于第三層for循環。所以為了提高效率,我們可以撤除第三層for循環(當然不是所有的三層循環都能這么做)。
方案二#
思路##查詢
搜索出所有的子序列,然后求和比較。(相對于方案一,去除了第三層求和的for循環)
代碼##
public static void maxSubSum2(int[] a) {
int maxSum = 0;
int startIndex = 0;
int endIndex = 0;
for (int i = 0; i < a.length; i++) {
int tempSum = 0;
for (int j = i; j < a.length; j++) {
tempSum += a[j];
if (maxSum < tempSum) {
maxSum = tempSum;
startIndex = i;
endIndex = j;
}
}
}
System.out.println("this subsequence is from " + startIndex + " to " + endIndex + ", max sum is " + maxSum);
}
分析##
此方案用了三層循環,第一層循環確定子序列的起始位子,第二層循環確定子序列的終止位子。通過第一層循環和第二層循環確定所有的子序列,然后通過第二層for循環求和。時間復雜度為O(N^2)。
方案三#
思路##
方案一和方案二的第一層循環用來確定子序列的起始位子,第二層循環用來確定子序列的結束位子。從兩層循環的起始位子看,都是從0開始,但是對于最大子序列,其起始位子的值不可能為負數;進一步看,如果最大子序列是AiAj,對于任意i<p<j,最大子序列的子序列AiAp的和一定大于0,所以對于任意從起始位子開始的子序列,若其和為負數,則丟棄,i繼續向前推進,直到數列遍歷完畢。
代碼##
public static void maxSubSum3(int a[]) {
int maxSum = 0, thisSum = 0;
for (int j = 0; j < a.length; j++) {
thisSum += a[j];
if (thisSum > maxSum) {
maxSum = thisSum;
} else if (thisSum < 0) {
thisSum = 0;
}
}
System.out.println(maxSum);
}
分析##
此方法對于i(子序列的其實位子)進行了優化,因為我們關注的只是最大子序列,所以不必搜尋出所有的子序列(和方案一、方案二的區別),只需找到所有Ai~Ap(i<p<j)的和不為負數的子序列,然而這種方式只需要一層循環即可,所以去掉了最外層的循環。然后求出這些子序列的和,然后比較。此算法的時間復雜度為O(N)。
該算法的一個附加優點是,它對數據只進行一次掃描,一旦a[i]被讀入處理,就不需要被記憶。因此,如果數組在磁盤上或通過互聯網傳播,那么它就可以被按順序讀入,在內存中就不必存儲數組的任何部分。不僅如此,在任意時刻,算法都能對它已經讀入的數據給出子序列問題的正確答案。具有這種 特性的算法叫做聯機算法(on-line algrithm)。僅需要常量空間并以線性時間運行的聯機算法幾乎是完美的。