当前位置: 首页 > news >正文

【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题的反向操作。需要理解罗马数字的构成规则,特别是减法表示法的处理。这是一个字符串解析和数学计算的经典问题。

算法分析

这道题的核心思想是减法规则识别,主要解法包括:

  1. 从右到左遍历法:从右向左遍历,比较相邻字符的值(推荐)
  2. 从左到右遍历法:从左向右遍历,预判下一个字符
  3. 数组映射优化:使用数组替代map,提高查找效率
  4. 递归方法:使用分治思想逐步解析
  5. 位运算优化:使用位运算优化比较操作

问题本质分析

罗马数字转整数
字符值映射
遍历策略
减法规则识别
I=1, V=5, X=10, L=50, C=100, D=500, M=1000
从右到左遍历
从左到右遍历
小值在大值左边时减法
建立映射关系
处理减法规则
最终结果计算
转换策略

从右到左遍历法详解

输入罗马数字字符串
初始化result=0, prevValue=0
从右到左遍历字符串
还有字符?
返回result
获取当前字符值
当前值 < 前一个值?
result -= 当前值
result += 当前值
更新prevValue
移动到下一个字符
减法规则处理
如IV=4, IX=9, XL=40等

转换过程可视化

输入: s = 'MCMXCIV'
从右到左遍历
第1步: V=5, prevValue=0, result=5
第2步: I=1, prevValue=5, 1<5, result=5-1=4
第3步: C=100, prevValue=1, 100>1, result=4+100=104
第4步: X=10, prevValue=100, 10<100, result=104-10=94
第5步: M=1000, prevValue=10, 1000>10, result=94+1000=1094
第6步: C=100, prevValue=1000, 100<1000, result=1094-100=994
第7步: M=1000, prevValue=100, 1000>100, result=994+1000=1994
最终结果: 1994

减法规则识别

减法规则
I在V或X左边
X在L或C左边
C在D或M左边
IV = 5-1 = 4
IX = 10-1 = 9
XL = 50-10 = 40
XC = 100-10 = 90
CD = 500-100 = 400
CM = 1000-100 = 900
特殊情况处理

各种解法对比

解法对比
从右到左遍历
从左到右遍历
数组映射优化
递归方法
位运算优化
时间O_n空间O_1
时间O_n空间O_1
时间O_n空间O_1
时间O_n空间O_n
时间O_n空间O_1
推荐解法
易于理解
性能最优
分治思想
位运算优化
平衡性能和可读性

算法流程图

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]

边界情况处理

边界情况
空字符串
单字符
两个字符减法
多个字符
返回0
直接返回字符值
检查减法规则
正常遍历处理
特殊情况处理

时间复杂度分析

时间复杂度分析
字符串遍历
每个字符处理
总时间复杂度
O_n
O_1
O_n
n是字符串长度
常数时间操作
线性时间复杂度
最优解法

空间复杂度分析

空间复杂度分析
额外空间使用
局部变量
最终空间复杂度
常数空间
result, prevValue等
O_1
原地算法
空间效率最优

关键优化点

优化策略
遍历方向选择
数据结构优化
比较操作优化
从右到左避免预判
数组替代map
位运算优化
简化逻辑

实际应用场景

应用场景
时钟显示
章节编号
版权年份
历史文献
罗马数字时钟解析
书籍章节编号转换
版权年份解析
古典文献数字转换
核心算法组件

测试用例设计

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

代码实现要点

  1. 遍历方向选择

    • 从右到左遍历:避免预判下一个字符
    • 从左到右遍历:需要检查下一个字符
  2. 减法规则识别

    • 当前值 < 前一个值时,需要减法
    • 当前值 >= 前一个值时,需要加法
  3. 数据结构选择

    • 使用map:代码清晰,查找O(1)
    • 使用数组:性能最优,内存连续
  4. 边界条件处理

    • 空字符串返回0
    • 单字符直接返回对应值
    • 确保所有情况都有正确的输出
  5. 性能优化

    • 一次遍历完成转换
    • 使用数组替代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)}
}
http://www.dtcms.com/a/335612.html

相关文章:

  • 不止于“渲染图”:从Adobe Stager到Three.js的交互式Web3D工作流
  • 安卓开发者自学鸿蒙开发4自定义组件
  • C#中List、Path、字符串操作等常用方法总结
  • fdisk的格式化分区和挂载(linux)
  • Collections.synchronizedList是如何将List变为线程安全的
  • 解决 OpenWrt 编译时报 toolchain/* failed to build 的错误
  • RK3588开发板Ubuntu系统烧录
  • Matlab课程实践——基于MATLAB设计的计算器软件(简单、科学、电工、矩阵及贷款计算)
  • Leetcode 15 java
  • 如何打造自己的工作室,实现快速开发
  • linux中已经启用的命令和替代命令
  • 【AI智能体】Dify 搭建发票识别助手操作实战详解
  • Windows 10共享打印机操作指南
  • 阶段二:7-上网行为安全概述
  • NestJS 手动集成TypeORM
  • 天国:拯救2 黄金版 MOD整合 送修改器+画质增强工具 免安装中文版
  • PyCharm与前沿技术集成指南:AI开发、云原生与大数据实战
  • EDMA(增强型直接内存访问)技术
  • 【LeetCode 热题 100】118. 杨辉三角
  • 大模型落地:从理论到实践的完整路径
  • RK3568平台开发系列讲解:PCIE trainning失败怎么办
  • 算法复杂度深度解析:时间与空间的权衡艺术
  • 编程算法实例-Armstrong数(阿姆斯特朗数)
  • TDengine IDMP 运维指南(1. 部署规划)
  • ThinkPHP8学习篇(三):控制器
  • 网络常识-SSE对比Websocket
  • 《Python学习之文件操作:从入门到精通》
  • 新的“MadeYouReset”方法利用 HTTP/2 进行隐秘的 DoS 攻击
  • 李宏毅NLP-11-语音合成
  • ReID/OSNet 算法模型量化转换实践