一、背景介绍
最近在牛客、leetcode 刷算法题时发现一个奇怪的问题,明明解题思路、所用算法与题解一致,并且在本地IDE运行是通过的,但是在牛客、leetcode 却没办法AC,出现一个叫做 运行超时 的异常。通过查阅资料、反复验证之后,找出了一个有效的解决办法。
二、解决办法
2.1 C/C++ 语言
针对使用 C/C++ 语言刷算法题遇到 运行超时异常 问题的同学,可以将输入/输出 换成 scanf/printf,此时极大概率就能通过
2.2 Java 语言
针对使用 Java 语言刷算法题时遇到 运行超时异常 问题的同学,就比较麻烦了,需要速记牢背一个 Java快速 IO 的模板,牢记这个模板之前我们需要先了解牛客、leetcode 在刷算法题时给我们提供的 ACM模式进行编程时(一般会给出两种模式进行编写代码,1、核心代码模式,即给出一个函数/方法,在此函数里书写题目的解决逻辑即可。2、ACM模式:给出Main类、main方法,其他没给出,需要书写解题时需用到的全部代码,包括引入的包名,都需要自己手动书写。一般工作时笔试中,做编程题时,给出的都是ACM模式),其进行I/O的流程。
2.2.1 ACM模式下 Java的I/O原理
对于 ACM模式 下,Java 进行 I/O的流程:
输入情况下(Java 使用 new Scanner(System.in) 进行输入数据):
输出情况下(Java 使用 System.out 进行输出数据):
从上述ACM模式下进行输入输出的原理图中,我们就能清楚知道使用 Java 自带的 Scanner / System.out 进行输入输出时,每次 输入/输出 都需要访问I/O设备,但访问一次I/O设备又是很慢的,因此代码量庞大的情况下,就会出现 运行超时异常问题。
Java 快速 I/O 模板:
import java.util.*;
import java.io.*;
public class Main
{
public static PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));
public static Read in = new Read();
public static void main(String[] args) throws IOException
{
// 写代码
out.close();
}
}
class Read // 自定义快速读入
{
StringTokenizer st = new StringTokenizer("");
BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
String next() throws IOException
{
while(!st.hasMoreTokens())
{
st = new StringTokenizer(bf.readLine());
}
return st.nextToken();
}
String nextLine() throws IOException
{
return bf.readLine();
}
int nextInt() throws IOException
{
return Integer.parseInt(next());
}
long nextLong() throws IOException
{
return Long.parseLong(next());
}
double nextDouble() throws IOException
{
return Double.parseDouble(next());
}
float nextFloat() throws IOException{
return Float.parseFloat(next());
}
}
希望使用 Java 作为 主编程语言的同学能够牢记这个 快速读写模板,不仅仅是我们日常在 牛客、leetcode 上进行 刷题 会用到,真正在找工作,进入笔试阶段书写编程题时,我们更会用到。所以这个模板必须要熟记于心,并且能够在3分钟内默写出来是最好的。
三、模板详情
可能有同学不清楚这个模板中的代码原理,我在这里简单解释一下,方便大家弄清原理后,更快速的记住这个模板。
1、首先,输出时,我们在模板中使用 字符流BufferedWriter 代替 字节流System.out 进行输出数据。
我们将 原来的 输出字节流 System.out 使用 输出字符流 BufferedWriter 代替,这是因为 字符流 自带 内存缓冲区,此时就会将 存储在I/O设备文件中的数据移到字符流自带的内存缓冲区里,每次输出时就在这个字符缓冲区内放数据,当不再进行输出时,一次性将数据放到 I/O设备的文件上即可。由于访问内存比访问I/O设备要快得多,同时,最后只需要访问一次I/O设备,这大大加快了写入的速度,造成 运行超时异常的概率大大降低。同时再在 BufferedWriter 上封装一层 PrintWriter,是由于 PrintWriter类中的输出形式与原来的输出形式一致,都是 print/printf, 此时输出时就可以使用 print/println 进行输出,不至于突然使用陌生的输出而出现陌生/不适应/不顺手的情况。当然,最后是否封装 printWriter 这一层都行,看自己的使用习惯。
2、使用 字符流 BufferedReader 代替 字节流 System.in 进行 输入数据。
我们自定义了一个 Read 类,通过这个类,实现 原来输入时使用 Scanner.in 类中的所有nextxx()方法。在这个 Read 类中,首先定义了一个 StringTokenizer 类型的 st 对象,使用 StringTokenizer 类的构造方法,传入了一个 空字符串,因此此时的 st 对象 是一个空字符串。
然后又定义了一个 BufferedReader 类,使用 字符流 替代 字节流,这里的原理跟上面输出使用的 BufferedWriter 原理都是一样的,他们都自带内存缓冲区,那么此时使用 nextxx() 方法进行输入数据时,会直接从内存缓冲区中拿取数据,同样的,访问内存的速度远比于访问I/O的速度要快得多,此时发生 运行超时异常 的概率就会大大降低。