前面几篇讲了 Go 的语法和数据结构,已经够你看懂大部分代码了。这篇补充一些在刷算法题时经常会用到的实用技巧,都是一些零散但重要的知识点。
ACM 模式 I/O 模板
很多 OJ 平台(包括本站)使用 ACM 模式,需要你自己处理输入输出。我们先来看最基本的 fmt.Scan 用法,然后介绍数据量大时必须用的 bufio 快读。
fmt.Scan 基本模板
fmt.Scan 是最简单的读取方式,它会自动跳过空白字符(空格、换行、制表符),一个一个读取数据:
注意 fmt.Scan 的参数必须传指针(加 &),这样才能把读到的值写入变量。这是 Go 的特点,习惯就好。
bufio.Scanner 快读(大数据量必备)
当输入数据量很大(比如几十万行),fmt.Scan 可能会超时。这时候需要用 bufio 来加速读取。有两种常用写法:
写法一:bufio.NewScanner + ScanWords
把输入按空白字符拆分成一个一个的 "单词",然后自己转换类型:
写法二:bufio.NewReader + fmt.Fscan
用 bufio.NewReader 包装标准输入,然后用 fmt.Fscan 从这个 Reader 中读取。这种写法和 fmt.Scan 用起来很像,但底层用了缓冲区,速度快很多:
reader := bufio.NewReader(os.Stdin)
var a, b int
fmt.Fscan(reader, &a, &b)一般来说,先用 fmt.Scan 写,遇到超时再换 bufio 方案。如果你一开始就想用快读,推荐写法二,改动最小。
排序
sort.Slice + 闭包自定义排序
Go 的排序靠 sort 包。对于 int 切片升序排列,可以直接用 sort.Ints;但如果要自定义排序规则,就需要 sort.Slice:
sort.Slice 的第二个参数是一个 less 函数,返回 true 表示 i 应该排在 j 前面。所以 nums[i] < nums[j] 是升序,nums[i] > nums[j] 是降序。
注意这个 less 函数是个闭包,它直接引用了外部的 nums 变量,这是 Go 排序的惯用写法。
对结构体切片排序
刷题时经常需要对结构体排序,比如按分数给学生排名:
多级排序
有时需要按多个条件排序,比如先按分数降序,分数相同再按名字字典序升序:
多级排序的套路就是:先比较第一优先级,不相等就直接返回结果;相等了再比较第二优先级,以此类推。
常用数学操作
math 包常用函数
Go 的 math 包提供了常用的数学函数,但有个大坑:math 包里的函数基本都只接受 float64 类型,用 int 的话必须手动转换。
关于 max 和 min,这是刷题中用得最多的操作。好消息是 Go 1.21 开始引入了内置的 max() 和 min() 函数,直接支持 int 类型,不需要任何导入,也不需要类型转换:
刷题时直接用内置的 max() 和 min() 就行,简单方便。math.Max 和 math.Min 是 float64 版本的,处理整数时不要用它们(还得来回转类型,又麻烦又容易出错)。
整数边界值
int32 和 int64 都有范围限制,超出范围会溢出。math 包提供了这些边界常量:
溢出防范
算法题中最常见的溢出场景是两个 int32 相乘,结果超出范围。解决方法很简单:
var a, b int32 = 100000, 100000
// 错误:int32 * int32 结果还是 int32,会溢出
wrong := a * b
// 正确:先转 int64 再乘
right := int64(a) * int64(b)Go 的 int 类型在 64 位系统上就是 int64,所以一般刷题时直接用 int 就够了,溢出的风险比 int32 小很多。如果题目明确说数值范围特别大,那就注意用 int64 并小心乘法。
优先队列模板(container/heap)
Go 标准库没有现成的优先队列,需要用 container/heap 包自己实现。虽然代码稍微多一点,但套路是固定的,背下来就行。
小顶堆模板代码
这段代码虽然看起来长,但核心就是实现五个方法:Len、Less、Swap、Push、Pop。其中 Len、Swap、Push、Pop 几乎是固定写法,你只需要关注 Less 方法——它决定了堆的排序方向。
使用时注意几点:
- 入堆:
heap.Push(h, val),不是h.Push(val) - 出堆:
heap.Pop(h),不是h.Pop() - 看堆顶:
(*h)[0],直接访问底层切片的第一个元素 - 判空:
h.Len() > 0
大顶堆(改 Less 方向)
把 Less 方法的比较方向反过来就行:
// 小顶堆
func (h IntHeap) Less(i, j int) bool { return h[i] < h[j] }
// 大顶堆:改成 >
func (h IntHeap) Less(i, j int) bool { return h[i] > h[j] }其他代码完全不用动。如果你的题目需要同时用大顶堆和小顶堆,可以定义两个不同的类型(比如 MinHeap 和 MaxHeap),只有 Less 不一样。
小结
这篇介绍的技巧在刷题中会反复用到。输入输出方面,先用 fmt.Scan,超时了换 bufio 快读。排序用 sort.Slice + 闭包,less 返回 true 表示前者排在前面。max() 和 min() 直接用内置的(Go 1.21+),不要用 math.Max/math.Min。计算可能越界时转 int64,或者直接用 int(64 位系统上等于 int64)。优先队列需要实现 heap.Interface 的五个方法,背下模板就行。
学到这里,Go 刷题所需的基础知识就全部介绍完了。接下来就可以开始刷题了,边刷边查,很快就能熟练起来。
后面两篇是选读内容:并发编程基础和接口与组合。如果你暂时只想刷算法题,可以先跳过。