Golang 刷算法题:标准输入处理的常见陷阱与解决方案

文章目录

前言

你好,我是醉墨居士。在最近使用 Golang 刷牛客网算法题的过程中,处理标准输入时遇到了不少棘手的问题。这些问题一度让我的解题之路变得磕磕绊绊,但通过不断地探索和尝试,我成功找到了有效的解决方案。现在,我迫不及待地想把这些经验分享给大家,希望能帮助各位在使用 Golang 刷题时更加顺畅,少走一些弯路

读取方式

我们主要读取标准输入的方式可以分为以下两种:

1. fmt.Scan

方法描述

fmt.Scan 是 Go 语言标准库 fmt 包提供的一个输入函数,它以空白字符(如空格、制表符、换行符等)作为输入值的分隔符。以下是一个简单的示例,该示例从标准输入读取两个整数,并计算它们的和:

go 复制代码
package main

import (
    "fmt"
    "io"
)

func main() {
    var a, b int
    // 循环读取输入
    for {
        // 尝试读取两个整数到变量 a 和 b
        _, err := fmt.Scan(&a, &b)
        // 如果遇到文件结束符(EOF),则跳出循环
        if err == io.EOF {
            break
        }
        // 打印两个整数的和
        fmt.Println(a + b)
    }
}
代码解释
  • 首先定义了两个整数变量 a 和 b,用于存储从标准输入读取的整数
  • 使用 for 循环不断尝试读取输入,fmt.Scan(&a, &b) 会尝试从标准输入读取两个整数,并将它们分别赋值给 a 和 b
  • 如果读取过程中遇到文件结束符(EOF),则 err 会等于 io.EOF,此时跳出循环
  • 若读取成功,则打印 a 和 b 的和

2. bufio.NewScanner(os.Stdin)

方法描述

bufio.NewScanner(os.Stdin) 用于创建一个从标准输入读取数据的扫描器,默认情况下它按行读取输入。以下是一个示例,同样实现读取两个整数并计算它们的和:

go 复制代码
package main

import (
    "bufio"
    "fmt"
    "os"
    "strconv"
    "strings"
)

func main() {
    var a, b int
    // 创建一个从标准输入读取数据的扫描器
    input := bufio.NewScanner(os.Stdin)
    // 循环读取每一行输入
    for input.Scan() {
        // 将当前行按空格分割成字符串切片
        parts := strings.Split(input.Text(), " ")
        // 将第一个字符串转换为整数并赋值给 a
        a, _ = strconv.Atoi(parts[0])
        // 将第二个字符串转换为整数并赋值给 b
        b, _ = strconv.Atoi(parts[1])
        // 打印两个整数的和
        fmt.Println(a + b)
    }
}
代码解释
  • 首先定义了两个整数变量 a 和 b
  • 使用 bufio.NewScanner(os.Stdin) 创建一个扫描器,用于从标准输入读取数据
  • 通过 for input.Scan() 循环逐行读取输入,input.Text() 可以获取当前行的文本内容
  • 使用 strings.Split(input.Text(), " ") 将当前行按空格分割成字符串切片
  • 使用 strconv.Atoi() 函数将分割后的字符串转换为整数,并分别赋值给 a 和 b
  • 最后打印 a 和 b 的和

踩坑及解决方案

在使用上述两种读取方式时,可能会遇到一些问题,下面将详细介绍这些问题及相应的解决方案

1. fmt.Scan 读取速度慢

问题描述

在一些算法题中,当算法的时间复杂度已经优化到极致时,使用 fmt.Scan() 读取输入数据仍然可能会导致超时。这是因为 fmt.Scan 在读取输入时会进行较多的格式化和解析操作,效率相对较低

解决方案

建议使用 bufio.NewScanner() 来替代 fmt.Scan。bufio.NewScanner() 采用了缓冲区机制,能够更高效地读取输入数据,从而避免因读取输入而导致的超时问题

2. bufio.NewScanner 的容量限制

问题描述

默认情况下,bufio.NewScanner 存在容量限制,其最大读取长度为 bufio.MaxScanTokenSize,即 64 * 1024 = 65536 个字节。如果读取的一行字符串的字节长度超过 65536,就会报错 bufio.Scanner: token too long,并且无法读取到完整的一行数据。

解决方案

可以使用 bufio.Scanner.Buffer() 方法来增加缓冲区的最大读取大小。以下是一个示例:

go 复制代码
package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    input := bufio.NewScanner(os.Stdin)
    // 增加缓冲区大小为 1024 * 1024 字节
    buffer := make([]byte, 1024*1024)
    input.Buffer(buffer, 1024*1024)
    for input.Scan() {
        fmt.Println(input.Text())
    }
}
代码解释
  • 首先创建一个 bufio.Scanner 对象
  • 然后使用 make([]byte, 1024*1024) 创建一个大小为 1024 * 1024 字节的缓冲区
  • 调用 input.Buffer(buffer, 1024*1024) 方法将缓冲区设置为 buffer,并将最大读取大小设置为 1024 * 1024 字节
  • 最后通过循环逐行读取输入并打印

3. bufio.NewScanner 处理多数字输入繁琐

问题描述

对于某些题目,如果输入的某一行包含多个数字,且这些数字以空格分隔,使用 bufio.NewScanner 时需要先读取一行,然后使用 strings.Split 方法进行分割,再逐个将字符串转换为数字,操作相对繁琐

解决方案

可以使用 bufio.Scanner.Split(bufio.ScanWords) 方法让输入以空白字符(空格、制表符、换行符等)作为分隔符,这样可以简化输入数据的处理。以下是一个示例:

go 复制代码
package main

import (
    "bufio"
    "fmt"
    "os"
    "strconv"
)

func main() {
    input := bufio.NewScanner(os.Stdin)
    // 设置分割方式为按单词分割
    input.Split(bufio.ScanWords)
    for input.Scan() {
        num, _ := strconv.Atoi(input.Text())
        fmt.Println(num)
    }
}
代码解释
  • 首先创建一个 bufio.Scanner 对象
  • 调用 input.Split(bufio.ScanWords) 方法将分割方式设置为按空白字符进行分割
  • 通过循环逐个读取单词,并将其转换为整数后打印
  • 需要注意的是,如果题目的输入数据是以 , 或者其他非空白字符进行分隔的,就不能使用这种方式。不过这种情况出现的概率相对较低

踩坑

  1. 问题 :使用fmt.Scan()读输入数据很慢,在某些题目中算法的时间复杂度已经优化到极致了,也会出现超时
    解决方案:使用bufio.NewScanner()

  2. 问题 :使用默认的bufio.NewScanner()是存在容量限制的,bufio.MaxScanTokenSize = 64*1024 = 65536个,也就是说默认情况下只能读取65536个byte的长度,如果读取的一行字符串的字节长度超过65536,就会报错bufio.Scanner: token too long,并且也无法读取到完整的一行数据
    解决方案:使用bufio.Scanner.Buffer() 增加缓冲区的最大读取大小

  3. 问题 :使用bufio.NewScanner对于某些题来说,如果输入的某一行会有多个数字按照空格分隔,我们需要首先读取一行,然后string.Split,然后在逐个索引下标,然后在字符串转数字,操作还是比较繁琐的
    解决方案:使用bufio.Scanner.Split(bufio.ScanWords) 让输入以空白字符(空格、制表符、换行符等)做为分隔,但请注意如果题目的输入数据是以 ',' 或者其它非空白字符进行分隔的,就不能使用这种方式,不过这种情况出现概率会很低,至少我到现在还没有遇到过,如果不幸遇到了,那就没办法了,只能每次读取数据进行略微繁琐的预处理

总结建议

1. 优先选择 bufio.NewScanner(os.Stdin)

在处理标准输入时,建议优先使用 bufio.NewScanner(os.Stdin) 来读取数据。因为它比 fmt.Scan 更快,能够有效避免在一些题目中因读取输入而造成的超时问题

2. 按需调整缓冲区大小

根据题目的具体要求,如果一次读取的输入数据长度可能超过 65536 字节,就要使用 bufio.Scanner.Buffer() 方法来增加缓冲区的大小,防止溢出

3. 灵活使用分割方式

对于一些输入数据完全使用空白字符分隔的题目,可以使用 bufio.Scanner.Split(bufio.ScanWords) 方法以空白字符进行分隔,简化输入数据的处理

最后

希望通过以上的分享,能让大家在使用 Golang 刷算法题时更加得心应手。如果在实践过程中遇到任何问题,欢迎随时交流探讨。祝大家刷题顺利,早日攻克各种难题

如果你还有遇到其它类型的问题,欢迎在评论区进行留言

相关推荐
大胖丫2 分钟前
vue 学习-vite api.js
开发语言·前端·javascript
遇见很ok3 分钟前
js中 ES6 新特性详解
开发语言·javascript·es6
陈浩源同学4 分钟前
学习 TypeScript 栈和队列数据结构
前端·算法
没有晚不了安12 分钟前
1.13作业
开发语言·python
布谷歌16 分钟前
Oops! 更改field的数据类型,影响到rabbitmq消费了...(有关于Java序列化)
java·开发语言·分布式·rabbitmq·java-rabbitmq
被程序耽误的胡先生21 分钟前
java中 kafka简单应用
java·开发语言·kafka
刀客12321 分钟前
python小项目编程-中级(1、图像处理)
开发语言·图像处理·python
Long_poem24 分钟前
【自学笔记】Spring Boot框架技术基础知识点总览-持续更新
spring boot·笔记·后端
卷卷的小趴菜学编程26 分钟前
c++之多态
c语言·开发语言·c++·面试·visual studio code
冷琴19961 小时前
基于Python+Vue开发的反诈视频宣传管理系统源代码
开发语言·vue.js·python