一、题目要求
1、输入一个整形数组,数组里有正数也有负数。
2、数组中连续的一个或多个整数组成一个子数组,每个子数组都有一个和。
3、求所有子数组的和的最大值。要求时间复杂度为O(n)
扩展要求(此时不再要求时间复杂度)
4、要求数组从文件读取。
5、如果输入的数组很大, 并且有很多大的数字, 就会产生比较大的结果 (考虑一下数的溢出), 请保证你的程序能正常输出。
6、另外, 如果输入文件的参数有错误, 这个程序应该能正常退出, 并显示相应的错误信息。 任何输入错误都不能导致你的程序崩溃
二、问题分析
对于前三点要求为了保证时间复杂度尽量小,我们的循环结构只能是单层。为了求最大子数组和,在循环过程中,我们要有当前子数组和"sum"与最大子数组和"maxsum"如果sum是小于零的,那一定不需要将该子数组sum计算在内,则sum应等于下一个数。而sum>0就需要继续判断该子数组和后一个数结合后的大小。如果有一个子数组的和大于了之前记录的最大子数组则赋值给它。由此我们得出简单的算法结构。
对于后三点的要求问题相对繁琐:
1、数据过大
2、从文本中获取数据到数组中
3、文本中数据结构可能有误
下面是我的解决方案
1、使用java类BigInteger来处理大数计算,BigInteger是封装好的可以直接用来处理无限大的整数计算的类但是使用过程中要注意到与其他格式间的转换。
2、首先从文本中逐行读取数据存入字符串,使用split函数将其拆分并存入字符串数组,然后再将字符串格式的数字转换成BigInteger类型存储。
3、结合我们第二点提出的获取数据的方式,我们需要在讲String类转换成BigInteger之前判断该数据是否含有非数字字符或者结构不正确字符比如“3a5”、“5-2”(这里负号允许存在但是只允许存在 于开头),这里为了显示错误信息,我选择了构建自定义错误类,用正则表达式判断每个切割后的字符串是否格式正确,正则表达式为:"^[-\\+]?[\\d]*$"
在编程中我又遇到了一些细节问题,比如分割String类时,我最初是split(" "),这就使得遇到多个空格时,会出现问题。及时查询资料,改为split("\\s+")后问题解决。
在测试过程中又遇到一个问题,如果文本开头为换行,或者文本中存在多个连续换行时,会报错提示大数长度不能为零,分析得出问题是由于切割后赋值给String数组的值可能是空值,增加判断条件,解决问题
三、源码
Main.java
package main; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.math.BigInteger; public class Main { private static BigInteger[] a = new BigInteger[5000]; private static int j = 0; public static void main(String[] args) { int i = 0; luru(); if (j == 0) { System.out.println("该文件不含数字"); System.exit(0); } System.out.println("录入结果如下"); for (i = 0; i < j; i++) System.out.print(a[i] + " "); System.out.println(""); BigInteger zero = new BigInteger("0"); BigInteger sum = a[0]; BigInteger maxsum = a[0]; for (i = 1; i < j; i++) { if (sum.compareTo(zero) == -1) sum = a[i]; else { sum = sum.add(a[i]); } if (sum.compareTo(maxsum) == 1) { maxsum = sum; } } System.out.println("该文件中最大数组和为" + maxsum); } static public void luru() { String pathname = "Shuzu.txt"; try (FileReader reader = new FileReader(pathname); BufferedReader br = new BufferedReader(reader)) { Checkletter check = new Checkletter(); String line; while ((line = br.readLine()) != null) { String[] data = line.split("\\s+"); for (int i = 0; i < data.length; i++) { try { check.check(data[i]); } catch (MyException e) {// 用自己的异常类来捕获异常 e.printStackTrace(); } if (!data[i].equals("")) { a[j] = new BigInteger(data[i]); j++; } } } } catch (IOException e) { e.printStackTrace(); } } }
MyException
package main; public class MyException extends Exception { private static final long serialVersionUID = 1L; // 提供无参数的构造方法 public MyException() { } // 提供一个有参数的构造方法,可自动生成 public MyException(String message) { super(message);// 把参数传递给Throwable的带String参数的构造方法 } }
Checkletter.java
package main; import java.util.regex.Pattern; public class Checkletter { public void check(String A) throws MyException {// 抛出自己的异常类 Pattern pattern=Pattern.compile("^[-\\+]?[\\d]*$"); pattern.matcher(A).matches(); if (pattern.matcher(A).matches()==false) { // 分数不合法时抛出异常 throw new MyException("文件中含有非数字字符");// new一个自己的异常类 } } }
四、运行结果
1、空文件时
2、文件中含有字母等特殊符号时
3、文件中含有大数
五、总结
在编程过程中,会遇到很多问题,他们在你脑海里可能会交错不清,解决这个问题会碰到另一个问题,而思考这个问题时又可能遇到下一个问题。这时候我们应该列出我们遇到的所有问题,像是递归那样,分析到最后一个问题,再逐层倒推。同时,对我而言掌握的基础知识不足,需要弥补的同时,在编程过程中,不应该妄想一气呵成,而是模块化编程,我先通过自设数组测试了我的核心算法,之后分别测试了String类和BigInter的转换、文本中数据的获取等。通过这种大问题分解成小问题的方式,我的调试过程变得很简单,也不失为一种“一气呵成”。