浙大数据结构mooc上的一个小例子,在此总结一下。
内容:PTA\最大子列和\java实现
前言:这个问题是我在学习浙大数据结构mooc上看到的,所以在此总结一下。
CSDN链接:https://blog.csdn.net/qq_42672532/article/details/102638421
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));
}
}
结果果不其然,后面计算量大以后超时了,太暴力了。
第二种方法:减少一次循环
原理:相同的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));
}
}
这一次结果正确了,没有超时。
测试点 提示 结果 耗时 内存
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));
}
}
结果耗时更短。
测试点 提示 结果 耗时 内存
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
相比较第二个方法,确实快了不少。
20 01-复杂度1 Java (openjdk) 667 ms 慕珩
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));
}
}
结果又比分而治之更快。
测试点 提示 结果 耗时 内存
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课真的很不错!!!(眼神暗示)
本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!