前言:這個問題是我在學習浙大數據結構mooc上看到的,所以在此總結一下。
PTA最大子列和問題:
問題描述:給定N個整數的序列{A1,A2,A3,……,An},求解子列和中最大的值。
例如給出{-2,11,-4,13,-5,-2}這樣一個序列,正確的最大子列和爲20
第一種方法:三層循環
原理:就是窮舉,按照順序一個一個算,很麻煩,但是很好懂。
時間複雜度:O(n^3)
import java.util.Scanner;
//最大子列和第一種方法
public class Main {
public static int maxSubseqSum1(int a[],int N){
int maxSum = 0;
for (int i = 0; i < N; i++) {
for (int j = i; j <N ; j++) {
int thisSum = 0;
for (int k = i; k <= j; k++) {
thisSum += a[k];
}
if(thisSum>maxSum){
maxSum = thisSum;
}
}
}
return maxSum;
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int N = scanner.nextInt();
int a[] = new int[N];
for (int i = 0; i < N; i++) {
a[i] = scanner.nextInt();
}
System.out.println(maxSubseqSum1(a,N));
}
}
結果果不其然,後面計算量大以後超時了,太暴力了。
提交時間 | 狀態 | 分數 | 題目 | 編譯器 | 耗時 | 用戶 |
---|---|---|---|---|---|---|
2019/10/19 14:40:44 |
部分正確 |
16 | 01-複雜度1 | Java (openjdk) | 374 ms | 慕珩 |
測試點 | 提示 | 結果 | 耗時 | 內存 |
---|---|---|---|---|
0 | sample 有正負,負數開頭結尾,最大和有更新 | 答案正確 | 92 ms | 10820 KB |
1 | 100個隨機數 | 答案正確 | 145 ms | 10768 KB |
2 | 1000個隨機數 | 答案正確 | 374 ms | 14424 KB |
3 | 10000個隨機數 | 運行超時 | -- | 0 KB |
4 | 100000個隨機數 | 運行超時 | -- | 0 KB |
第二種方法:減少一次循環
原理:相同的i不同的j,計算和的時候可以不從頭開始加,直接按照剛纔的那個加下一個數即可。
時間複雜度:O(n^2)
import java.util.Scanner;
//最大子列和第二種方法
public class Main {
public static int maxSubseqSum1(int a[],int N){
int maxSum = 0;
//i是左端,j是右端
for (int i = 0; i < N; i++) {
int thisSum = 0;
//對於相同的i,不同的j,下一次直接加在上一次的結果上。
for (int j = i; j <N ; j++) {
thisSum += a[j];
if(thisSum>maxSum){
maxSum = thisSum;
}
}
}
return maxSum;
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int N = scanner.nextInt();
int a[] = new int[N];
for (int i = 0; i < N; i++) {
a[i] = scanner.nextInt();
}
System.out.println(maxSubseqSum1(a,N));
}
}
這一次結果正確了,沒有超時。
提交時間 | 狀態 | 分數 | 題目 | 編譯器 | 耗時 | 用戶 |
---|---|---|---|---|---|---|
2019/10/19 14:54:27 |
答案正確 |
20 | 01-複雜度1 | Java (openjdk) | 5642 ms | 慕珩 |
測試點 | 提示 | 結果 | 耗時 | 內存 |
---|---|---|---|---|
0 | sample 有正負,負數開頭結尾,最大和有更新 | 答案正確 | 115 ms | 10920 KB |
1 | 100個隨機數 | 答案正確 | 146 ms | 10856 KB |
2 | 1000個隨機數 | 答案正確 | 199 ms | 14184 KB |
3 | 10000個隨機數 | 答案正確 | 360 ms | 23524 KB |
4 | 100000個隨機數 | 答案正確 | 5642 ms | 52788 KB |
第三種方法:分而治之
原理:分成左右兩個部分,求兩邊的最大和,然後求出跨越兩邊的最大和,一直遞歸。
時間複雜度:O(NlogN)
import java.util.Scanner;
//最大子列和第三種方法
//代碼是根據mooc上寫出來的,有一部分稍作改動
public class Main {
//三個數字裏找最大
public static int foundMax(int a,int b,int c){
int max = 0;
if(a>max){
max = a;
}
if(b>max){
max = b;
}
if(c>max){
max = c;
}
return max;
}
public static int divideAndConquer(int a[],int left,int right){
int maxSum = 0;
//定義左邊最大和,右邊最大和,跨越兩邊掃描的左邊最大和,右邊最大和
int rightMaxSum ,leftMaxSum ;
int maxLeftBorderSum, maxRightBorderSum;
int maxBorderSum;
//當前左邊和,當前右邊和
int leftBorderSum =0 , rightBorderSum = 0;
//中心位置和數字
int center, i;
//只有一個數字的時候,最大子列和就是這個數
// 也是遞歸的終止條件
if(left==right){
if(a[left]>0)return a[left];
else{return 0;}
}
//分
center = (right+left)/2;
//遞歸得到左邊最大和和右邊最大和
leftMaxSum = divideAndConquer(a,left,center);
rightMaxSum = divideAndConquer(a,center+1,right);
//跨中點的最大子列和
maxLeftBorderSum = 0;
maxRightBorderSum = 0;
//從中點向左掃描
for (i = center; i>=left; i--) {
leftBorderSum += a[i];
if(leftBorderSum>maxLeftBorderSum){
maxLeftBorderSum = leftBorderSum;
}
}
//從中點向右掃描
for (i =center+1; i<=right ; i++) {
rightBorderSum += a[i];
if(rightBorderSum>maxRightBorderSum){
maxRightBorderSum = rightBorderSum;
}
}
maxBorderSum = maxLeftBorderSum+maxRightBorderSum;
maxSum = foundMax(maxBorderSum,leftMaxSum,rightMaxSum);
return maxSum;
}
public static int maxSubseqSum3(int a[],int N){
return divideAndConquer(a,0,N-1);
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int N = scanner.nextInt();
int a[] = new int[N];
for (int i = 0; i < N; i++) {
a[i] = scanner.nextInt();
}
System.out.println(maxSubseqSum3(a,N));
}
}
結果耗時更短。
提交時間 | 狀態 | 分數 | 題目 | 編譯器 | 耗時 | 用戶 |
---|---|---|---|---|---|---|
2019/10/19 15:33:04 |
答案正確 |
20 | 01-複雜度1 | Java (openjdk) | 667 ms | 慕珩 |
測試點 | 提示 | 結果 | 耗時 | 內存 |
---|---|---|---|---|
0 | sample 有正負,負數開頭結尾,最大和有更新 | 答案正確 | 120 ms | 10832 KB |
1 | 100個隨機數 | 答案正確 | 130 ms | 10704 KB |
2 | 1000個隨機數 | 答案正確 | 169 ms | 12692 KB |
3 | 10000個隨機數 | 答案正確 | 293 ms | 21396 KB |
4 | 100000個隨機數 | 答案正確 | 667 ms | 50196 KB |
相比較第二個方法,確實快了不少。
2019/10/19 15:33:04 |
答案正確 |
20 | 01-複雜度1 | Java (openjdk) | 667 ms | 慕珩 |
2019/10/19 14:54:27 |
答案正確 |
20 | 01-複雜度1 | Java (openjdk) | 5642 ms | 慕珩 |
第四種方法:在線處理
原理:從第一個數開始,向右累加,如果有更大的則更新當前結果,如果變成負數則捨棄前面的,從下一個開始。
(因爲負數不可能使後面的子列和增加。)
時間複雜度:O(N) (應該是最快的了,因爲你總要把所有的數字掃描一遍吧)
import java.util.Scanner;
//最大子列和第4種方法
//代碼是根據mooc上寫出來的,有一部分稍作改動
public class Main {
public static int onlineFound(int a[],int N){
int thisSum =0,maxSum = 0;
for (int i = 0; i < N; i++) {
thisSum += a[i];
if(thisSum>maxSum){
maxSum = thisSum;
}
else if(thisSum<0){
thisSum = 0;
}
}
return maxSum;
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int N = scanner.nextInt();
int a[] = new int[N];
for (int i = 0; i < N; i++) {
a[i] = scanner.nextInt();
}
System.out.println(onlineFound(a,N));
}
}
結果又比分而治之更快。
提交時間 | 狀態 | 分數 | 題目 | 編譯器 | 耗時 | 用戶 |
---|---|---|---|---|---|---|
2019/10/19 15:40:52 |
答案正確 |
20 | 01-複雜度1 | Java (openjdk) | 543 ms | 慕珩 |
測試點 | 提示 | 結果 | 耗時 | 內存 |
---|---|---|---|---|
0 | sample 有正負,負數開頭結尾,最大和有更新 | 答案正確 | 121 ms | 10952 KB |
1 | 100個隨機數 | 答案正確 | 119 ms | 10796 KB |
2 | 1000個隨機數 | 答案正確 | 129 ms | 13336 KB |
3 | 10000個隨機數 | 答案正確 | 198 ms | 22740 KB |
4 | 100000個隨機數 | 答案正確 | 543 ms | 52336 KB |
2、3、4方法相比:
4 | 543 ms |
3 | 667 ms |
2 | 5642 ms |
寫在最後:因爲不太喜歡C語言(因爲指針那些的沒學好= =),所以學數據結構就想嘗試用java寫出來,不過我猜以後考研還會再用c寫(想吐)。
這個最大子列和問題雖然是對着mooc上的思路來的,但是每一步現在都很清晰明瞭了,自己寫也可以寫出來了,也算是一大突破。(捂臉,以前太懶太笨了,寫不出來。)
另外,吼一嗓子:浙大的mooc課真的很不錯!!!(眼神暗示)