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 刷算法题时更加得心应手。如果在实践过程中遇到任何问题,欢迎随时交流探讨。祝大家刷题顺利,早日攻克各种难题

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

相关推荐
bobz9651 小时前
vxlan 为什么一定要封装在 udp 报文里?
后端
bobz9651 小时前
vxlan 直接使用 ip 层封装是否可以?
后端
郑道3 小时前
Docker 在 macOS 下的安装与 Gitea 部署经验总结
后端
3Katrina3 小时前
妈妈再也不用担心我的课设了---Vibe Coding帮你实现期末课设!
前端·后端·设计
汪子熙3 小时前
HSQLDB 数据库锁获取失败深度解析
数据库·后端
高松燈3 小时前
若伊项目学习 后端分页源码分析
后端·架构
没逻辑4 小时前
主流消息队列模型与选型对比(RabbitMQ / Kafka / RocketMQ)
后端·消息队列
倚栏听风雨4 小时前
SwingUtilities.invokeLater 详解
后端
Java中文社群4 小时前
AI实战:一键生成数字人视频!
java·人工智能·后端
王中阳Go5 小时前
从超市收银到航空调度:贪心算法如何破解生活中的最优决策谜题?
java·后端·算法