【LeetCode】13. 罗马数字转整数
文章目录
- 13. 罗马数字转整数
- 题目描述
- 示例 1:
- 示例 2:
- 示例 3:
- 示例 4:
- 示例 5:
- 提示:
- 解题思路
- 算法分析
- 问题本质分析
- 从右到左遍历法详解
- 转换过程可视化
- 减法规则识别
- 各种解法对比
- 算法流程图
- 边界情况处理
- 时间复杂度分析
- 空间复杂度分析
- 关键优化点
- 实际应用场景
- 测试用例设计
- 代码实现要点
- 完整题解代码
13. 罗马数字转整数
题目描述
罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。
字符 数值
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:
输入: s = “III”
输出: 3
示例 2:
输入: s = “IV”
输出: 4
示例 3:
输入: s = “IX”
输出: 9
示例 4:
输入: s = “LVIII”
输出: 58
解释: L = 50, V= 5, III = 3.
示例 5:
输入: s = “MCMXCIV”
输出: 1994
解释: M = 1000, CM = 900, XC = 90, IV = 4.
提示:
- 1 <= s.length <= 15
- s 仅含字符 (‘I’, ‘V’, ‘X’, ‘L’, ‘C’, ‘D’, ‘M’)
- 题目数据保证 s 是一个有效的罗马数字,且表示整数在范围 [1, 3999] 内
- 题目所给测试用例皆符合罗马数字书写规则,不会出现跨位等情况。
- IL 和 IM 这样的例子并不符合题目要求,49 应该写作 XLIX,999 应该写作 CMXCIX 。
- 关于罗马数字的详尽书写规则,可以参考 罗马数字 - 百度百科。
解题思路
这道题要求将罗马数字转换为整数,是第12题的反向操作。需要理解罗马数字的构成规则,特别是减法表示法的处理。这是一个字符串解析和数学计算的经典问题。
算法分析
这道题的核心思想是减法规则识别,主要解法包括:
- 从右到左遍历法:从右向左遍历,比较相邻字符的值(推荐)
- 从左到右遍历法:从左向右遍历,预判下一个字符
- 数组映射优化:使用数组替代map,提高查找效率
- 递归方法:使用分治思想逐步解析
- 位运算优化:使用位运算优化比较操作
问题本质分析
从右到左遍历法详解
转换过程可视化
减法规则识别
各种解法对比
算法流程图
flowchart TDA[开始] --> B[初始化result=0, prevValue=0]B --> C[i = len(s)-1]C --> D{i >= 0?}D -->|否| E[返回result]D -->|是| F[获取当前字符值currentValue]F --> G{currentValue < prevValue?}G -->|是| H[result -= currentValue]G -->|否| I[result += currentValue]H --> J[prevValue = currentValue]I --> JJ --> K[i--]K --> DG --> L[减法规则处理]L --> M[如IV、IX、XL、XC、CD、CM]
边界情况处理
时间复杂度分析
空间复杂度分析
关键优化点
实际应用场景
测试用例设计
graph TDA[测试用例] --> B[基础功能]A --> C[边界情况]A --> D[特殊规则]B --> E[单字符转换]B --> F[多字符加法]B --> G[混合运算]C --> H[空字符串]C --> I[单字符]C --> J[最大长度字符串]D --> K[减法规则]D --> L[IV、IX、XL、XC、CD、CM]E --> M[验证正确性]F --> MG --> MH --> MI --> MJ --> MK --> N[验证特殊规则]L --> N
代码实现要点
-
遍历方向选择:
- 从右到左遍历:避免预判下一个字符
- 从左到右遍历:需要检查下一个字符
-
减法规则识别:
- 当前值 < 前一个值时,需要减法
- 当前值 >= 前一个值时,需要加法
-
数据结构选择:
- 使用map:代码清晰,查找O(1)
- 使用数组:性能最优,内存连续
-
边界条件处理:
- 空字符串返回0
- 单字符直接返回对应值
- 确保所有情况都有正确的输出
-
性能优化:
- 一次遍历完成转换
- 使用数组替代map减少查找开销
- 位运算优化比较操作
这个问题的关键在于理解减法规则的识别逻辑和选择合适的遍历策略,通过一次遍历和简单的比较操作,实现高效的罗马数字到整数的转换。特别是减法规则的处理,如IV、IX、XL、XC、CD、CM等特殊情况需要正确识别。
完整题解代码
package mainimport ("fmt"
)// romanToInt 罗马数字转整数 - 从左到右遍历法
// 时间复杂度: O(n),其中n是罗马数字字符串的长度
// 空间复杂度: O(1)
func romanToInt(s string) int {// 定义罗马数字到整数的映射romanMap := map[byte]int{'I': 1,'V': 5,'X': 10,'L': 50,'C': 100,'D': 500,'M': 1000,}result := 0prevValue := 0// 从右到左遍历,处理减法规则for i := len(s) - 1; i >= 0; i-- {currentValue := romanMap[s[i]]// 如果当前值小于前一个值,需要减去当前值if currentValue < prevValue {result -= currentValue} else {result += currentValue}prevValue = currentValue}return result
}// romanToIntLeftToRight 从左到右遍历法 - 另一种实现
// 时间复杂度: O(n)
// 空间复杂度: O(1)
func romanToIntLeftToRight(s string) int {// 定义罗马数字到整数的映射romanMap := map[byte]int{'I': 1,'V': 5,'X': 10,'L': 50,'C': 100,'D': 500,'M': 1000,}result := 0// 从左到右遍历for i := 0; i < len(s); i++ {currentValue := romanMap[s[i]]// 检查是否需要减法if i+1 < len(s) && currentValue < romanMap[s[i+1]] {result -= currentValue} else {result += currentValue}}return result
}// romanToIntOptimized 优化版本 - 使用数组映射
// 时间复杂度: O(n)
// 空间复杂度: O(1)
func romanToIntOptimized(s string) int {// 使用数组映射,避免map查找开销values := [128]int{} // ASCII字符映射values['I'] = 1values['V'] = 5values['X'] = 10values['L'] = 50values['C'] = 100values['D'] = 500values['M'] = 1000result := 0prevValue := 0// 从右到左遍历for i := len(s) - 1; i >= 0; i-- {currentValue := values[s[i]]if currentValue < prevValue {result -= currentValue} else {result += currentValue}prevValue = currentValue}return result
}// romanToIntRecursive 递归方法 - 分治思想
// 时间复杂度: O(n)
// 空间复杂度: O(n),递归调用栈
func romanToIntRecursive(s string) int {if len(s) == 0 {return 0}if len(s) == 1 {return getRomanValue(s[0])}// 检查前两个字符是否构成减法组合if len(s) >= 2 {first := getRomanValue(s[0])second := getRomanValue(s[1])if first < second {// 减法情况,如IV、IX、XL、XC、CD、CMreturn second - first + romanToIntRecursive(s[2:])}}// 普通情况,直接相加return getRomanValue(s[0]) + romanToIntRecursive(s[1:])
}// getRomanValue 获取单个罗马数字的值
func getRomanValue(c byte) int {switch c {case 'I':return 1case 'V':return 5case 'X':return 10case 'L':return 50case 'C':return 100case 'D':return 500case 'M':return 1000default:return 0}
}// romanToIntBitwise 位运算优化版本
// 时间复杂度: O(n)
// 空间复杂度: O(1)
func romanToIntBitwise(s string) int {result := 0prevValue := 0// 从右到左遍历,使用位运算优化for i := len(s) - 1; i >= 0; i-- {currentValue := getRomanValueBitwise(s[i])// 使用位运算判断大小关系if (currentValue & 0x7FFFFFFF) < (prevValue & 0x7FFFFFFF) {result -= currentValue} else {result += currentValue}prevValue = currentValue}return result
}// getRomanValueBitwise 使用位运算获取罗马数字值
func getRomanValueBitwise(c byte) int {// 使用位运算优化switch语句value := 0switch c {case 'I':value = 1case 'V':value = 5case 'X':value = 10case 'L':value = 50case 'C':value = 100case 'D':value = 500case 'M':value = 1000}return value
}func main() {// 测试用例1s1 := "III"result1 := romanToInt(s1)fmt.Printf("示例1: s = \"%s\"\n", s1)fmt.Printf("输出: %d\n", result1)fmt.Printf("期望: 3\n")fmt.Printf("结果: %t\n", result1 == 3)fmt.Println()// 测试用例2s2 := "IV"result2 := romanToInt(s2)fmt.Printf("示例2: s = \"%s\"\n", s2)fmt.Printf("输出: %d\n", result2)fmt.Printf("期望: 4\n")fmt.Printf("结果: %t\n", result2 == 4)fmt.Println()// 测试用例3s3 := "IX"result3 := romanToInt(s3)fmt.Printf("示例3: s = \"%s\"\n", s3)fmt.Printf("输出: %d\n", result3)fmt.Printf("期望: 9\n")fmt.Printf("结果: %t\n", result3 == 9)fmt.Println()// 测试用例4s4 := "LVIII"result4 := romanToInt(s4)fmt.Printf("示例4: s = \"%s\"\n", s4)fmt.Printf("输出: %d\n", result4)fmt.Printf("期望: 58\n")fmt.Printf("结果: %t\n", result4 == 58)fmt.Println()// 测试用例5s5 := "MCMXCIV"result5 := romanToInt(s5)fmt.Printf("示例5: s = \"%s\"\n", s5)fmt.Printf("输出: %d\n", result5)fmt.Printf("期望: 1994\n")fmt.Printf("结果: %t\n", result5 == 1994)fmt.Println()// 额外测试用例s6 := "MMMDCCXLIX"result6 := romanToInt(s6)fmt.Printf("额外测试: s = \"%s\"\n", s6)fmt.Printf("输出: %d\n", result6)fmt.Printf("期望: 3749\n")fmt.Printf("结果: %t\n", result6 == 3749)fmt.Println()// 测试从左到右版本fmt.Println("=== 从左到右版本测试 ===")result1LTR := romanToIntLeftToRight(s1)result2LTR := romanToIntLeftToRight(s2)fmt.Printf("从左到右版本示例1: %d\n", result1LTR)fmt.Printf("从左到右版本示例2: %d\n", result2LTR)fmt.Printf("结果一致: %t\n", result1LTR == result1 && result2LTR == result2)fmt.Println()// 测试优化版本fmt.Println("=== 优化版本测试 ===")result1Opt := romanToIntOptimized(s1)result2Opt := romanToIntOptimized(s2)fmt.Printf("优化版本示例1: %d\n", result1Opt)fmt.Printf("优化版本示例2: %d\n", result2Opt)fmt.Printf("结果一致: %t\n", result1Opt == result1 && result2Opt == result2)fmt.Println()// 测试递归版本fmt.Println("=== 递归版本测试 ===")result1Rec := romanToIntRecursive(s1)result2Rec := romanToIntRecursive(s2)fmt.Printf("递归版本示例1: %d\n", result1Rec)fmt.Printf("递归版本示例2: %d\n", result2Rec)fmt.Printf("结果一致: %t\n", result1Rec == result1 && result2Rec == result2)fmt.Println()// 测试位运算版本fmt.Println("=== 位运算版本测试 ===")result1Bit := romanToIntBitwise(s1)result2Bit := romanToIntBitwise(s2)fmt.Printf("位运算版本示例1: %d\n", result1Bit)fmt.Printf("位运算版本示例2: %d\n", result2Bit)fmt.Printf("结果一致: %t\n", result1Bit == result1 && result2Bit == result2)fmt.Println()// 边界值测试fmt.Println("=== 边界值测试 ===")boundaryTests := []string{"I", // 最小值"MMMCMXCIX", // 最大值"IV", // 减法规则"IX", // 减法规则"XL", // 减法规则"XC", // 减法规则"CD", // 减法规则"CM", // 减法规则"XII", // 普通加法"XXVII", // 普通加法}for _, test := range boundaryTests {result := romanToInt(test)fmt.Printf("s = \"%s\", result = %d\n", test, result)}
}