LeetCode-Go 题解精讲:13. Roman to Integer 罗马数字转整数(Go 实现)
【免费下载链接】LeetCode-Go✅ Solutions to LeetCode by Go, 100% test coverage, runtime beats 100% | LeetCode 题解项目地址: https://gitcode.com/GitHub_Trending/le/LeetCode-Go
导读
本文以 LeetCode 第 13 题Roman to Integer(罗马数字转整数)为核心,完整讲解题目规则、六类减法特例,并逐行剖析 LeetCode-Go 仓库中romanToInt的 Go 实现与配套测试。读完本文,你将掌握罗马数字「大数在前为加、小数在前为减」的解析规律,理解单次线性扫描即可完成转换的算法思路,并能直接运行本仓库的测试用例验证正确性。
一、题目描述
罗马数字由以下七种不同符号表示:
| 符号 Symbol | 数值 Value |
|---|---|
I | 1 |
V | 5 |
X | 10 |
L | 50 |
C | 100 |
D | 500 |
M | 1000 |
例如,数字 2 写作II,即两个 1 相加;12 写作XII,即X + II;27 写作XXVII,即XX + V + II。
罗马数字通常按照从大到小、从左到右书写。但数字 4 并不写作IIII,而是写作IV——因为 1 在 5 的前面,表示用 5 减去 1 得到 4。同理,数字 9 写作IX。这种「减法」规则只适用于以下六种情况:
I可以放在V(5)和X(10)左边,表示 4 和 9;X可以放在L(50)和C(100)左边,表示 40 和 90;C可以放在D(500)和M(1000)左边,表示 400 和 900。
题目要求:给定一个罗马数字,将其转换成整数。输入保证在 1 到 3999 的范围内。
二、官方示例
| 示例 | 输入 | 输出 | 说明 |
|---|---|---|---|
| 示例 1 | "III" | 3 | 三个 1 相加 |
| 示例 2 | "IV" | 4 | 1 在 5 前,做减法 |
| 示例 3 | "IX" | 9 | 1 在 10 前,做减法 |
| 示例 4 | "LVIII" | 58 | L = 50, V = 5, III = 3 |
| 示例 5 | "MCMXCIV" | 1994 | M = 1000, CM = 900, XC = 90, IV = 4 |
以示例 5 的MCMXCIV为例,它可拆解为M + CM + XC + IV,即1000 + 900 + 90 + 4 = 1994,正是减法规则与加法规则混合使用的典型场景。
三、解题思路
本题属于简单题:按照题目给出的罗马数字字符数值,逐位计算出对应的十进制数即可。
关键观察是:「小数在左」意味着减法,「小数在右」意味着加法。因此有两种经典解法:
- 从左到右扫描:比较当前字符与下一字符的数值,若当前值小于下一值,则减去当前值,否则加上当前值;
- 从右到左扫描(本仓库采用):维护一个
lastint记录「右边相邻字符」的数值,若当前字符数值小于右边字符数值,说明出现了减法组合,做减法;否则做加法。
从右到左扫描的优势是只需一次遍历即可完成全部处理,时间复杂度 O(n),空间复杂度 O(1),n 为字符串长度(题目限制 n 最大对应 3999,即最多约 15 个字符)。
四、Go 实现逐行剖析
LeetCode-Go 仓库中该题的实现位于 leetcode/0013.Roman-to-Integer/13. Roman to Integer.go,完整代码如下:
package leetcode var roman = map[string]int{ "I": 1, "V": 5, "X": 10, "L": 50, "C": 100, "D": 500, "M": 1000, } func romanToInt(s string) int { if s == "" { return 0 } num, lastint, total := 0, 0, 0 for i := 0; i < len(s); i++ { char := s[len(s)-(i+1) : len(s)-i] num = roman[char] if num < lastint { total = total - num } else { total = total + num } lastint = num } return total }4.1 符号表roman
代码通过包级变量roman建立「字符 → 数值」的映射表,与题目给出的七种符号一一对应。这种 map 查表方式可读性强、扩展方便,是处理有限字符集映射的常用做法。
4.2 空串防护
if s == "" { return 0 }对空字符串直接返回 0,避免后续切片操作越界。这一点也在仓库测试用例中得到了覆盖(见下文测试章节)。
4.3 从右向左的减法判定
num, lastint, total := 0, 0, 0 for i := 0; i < len(s); i++ { char := s[len(s)-(i+1) : len(s)-i] num = roman[char] if num < lastint { total = total - num } else { total = total + num } lastint = num }核心逻辑分四步:
- 反向取字符:
s[len(s)-(i+1) : len(s)-i]从字符串末尾向开头逐个截取单字符(用切片而非char字节,是为了直接作为 map 的 string 键); - 查表取值:
num = roman[char]得到当前字符数值; - 比较并累加:若
num < lastint,说明当前字符在它右边较大数字的左边,构成减法组合(如IV、IX、XC、CM),从总数中减去num;否则直接加上num; - 更新
lastint:记录已处理的右边字符数值,供下一次迭代比较。
4.3.1 以IV为例推演
- 反向取到
V:num = 5,lastint = 0,5 >= 0,total = 5,lastint = 5; - 反向取到
I:num = 1,1 < 5,判定为减法,total = 5 - 1 = 4。
最终返回 4,与题目示例一致。
4.3.2 以MCMXCIV为例推演
从右往左依次处理V → I → C → X → M → C → M:
| 处理字符 | num | lastint(右边值) | 运算 | total |
|---|---|---|---|---|
V | 5 | 0 | +5 | 5 |
I | 1 | 5 | -1(1 < 5) | 4 |
C | 100 | 1 | +100 | 104 |
X | 10 | 100 | -10(10 < 100) | 94 |
M | 1000 | 10 | +1000 | 1094 |
C | 100 | 1000 | -100(100 < 1000) | 994 |
M | 1000 | 100 | +1000 | 1994 |
最终返回 1994,与题目示例 5 完全吻合。可以看到,减法组合(IV、XC、CM)都在反向扫描中被自动识别。
五、测试用例与验证
该题测试位于 leetcode/0013.Roman-to-Integer/13. Roman to Integer_test.go,采用「参数-答案」结构体驱动的表驱动测试:
type question13 struct { para13 ans13 } type para13 struct { one string } type ans13 struct { one int } func Test_Problem13(t *testing.T) { qs := []question13{ {para13{"III"}, ans13{3}}, {para13{"IV"}, ans13{4}}, {para13{"IX"}, ans13{9}}, {para13{"LVIII"}, ans13{58}}, {para13{"MCMXCIV"}, ans13{1994}}, {para13{"MCMXICIVI"}, ans13{2014}}, {para13{""}, ans13{0}}, } for _, q := range qs { a, p := q.ans13, q.para13 got := romanToInt(p.one) if got != a.one { t.Fatalf("input %q: got %d, want %d", p.one, got, a.one) } } }5.1 用例覆盖分析
测试用例不仅覆盖了题目给出的全部五个示例(III、IV、IX、LVIII、MCMXCIV),还额外补充了两个边界场景:
"MCMXICIVI" → 2014:混合长字符串的鲁棒性验证;"" → 0:空串边界,直接对应源码第 15~17 行的空串防护分支。
5.2 运行方式
在仓库根目录下运行单题测试:
go test -v ./leetcode/0013.Roman-to-Integer/若希望运行全部题目的测试并输出覆盖率,可直接使用仓库自带的 gotest.sh 脚本(其内部执行go test -covermode=atomic -coverprofile=coverage.txt ./leetcode/...):
bash gotest.sh六、与逆运算 12. Integer to Roman 的对照
罗马数字相关的另一道题是 LeetCode 第 12 题 Integer to Roman(整数转罗马数字),本仓库的实现位于 leetcode/0012.Integer-to-Roman/12. Integer to Roman.go:
func intToRoman(num int) string { values := []int{1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1} symbols := []string{"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"} res, i := "", 0 for num != 0 { for values[i] > num { i++ } num -= values[i] res += symbols[i] } return res }两道题互为逆运算,可对照学习:
- 第 13 题将罗马数字解析为整数,核心是「识别减法组合」(小数在前即减);
- 第 12 题将整数编码为罗马数字,核心是把四类减法组合(
CM、CD、XC、XL、IX、IV)与常规符号一起按降序贪心取值。
值得注意的是,第 12 题把900/400/90/40/9/4等减法组合直接编码为独立符号单元,这恰好印证了第 13 题反向扫描时「num < lastint即做减法」的判定与罗马数字本身的编码结构是自洽的——一个合法罗马数字串中,只要出现小数在前的相邻对,必然属于六类减法特例之一。
七、小结
本题的关键要点可归纳为:
- 规则本质:罗马数字「左减右加」,六类减法特例覆盖所有小数在前的情况;
- 算法核心:一次反向扫描,通过与右边已处理字符的数值比较即可同时处理加减,无需预判组合;
- 实现技巧:map 符号表 + 切片取字符 +
lastint游标,代码简洁且空间复杂度 O(1); - 工程规范:仓库代码遵循 Google Golang Style Guide,配套表驱动测试覆盖示例与边界,可通过 gotest.sh 一键运行全部测试并生成覆盖率报告。
无论是准备面试还是巩固字符串解析基本功,将本仓库的 源码实现 与 测试用例 对照研读,都是最快掌握该题解法的路径。
【免费下载链接】LeetCode-Go✅ Solutions to LeetCode by Go, 100% test coverage, runtime beats 100% | LeetCode 题解项目地址: https://gitcode.com/GitHub_Trending/le/LeetCode-Go
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考