LeetCode 13 罗马数字转整数

1. 题目

13. 罗马数字转整数 - 力扣(LeetCode)

题目描述

罗马数字包含以下七种字符:IVXLCDM

字符 数值
I 1
V 5
X 10
L 50
C 100
D 500
M 1000

特殊规则:

  1. 正常情况大数在右,直接相加,如 III=3VI=6

  2. 减法特例:IV/X 前 =4/9;XL/C 前 = 40/90;CD/M 前 = 400/900。

给定合法罗马字符串,转换为对应整数。

示例

输入:III → 3

输入:IV → 4

输入:IX → 9

输入:LVIII → 58

输入:MCMXCIV → 1994

约束

  • 1≤s .length≤15

  • s 仅由 I,V,X,L,C,D,M 组成,输入保证合法罗马数字

2. 最佳解题思路描述(哈希映射 + 后项比较,极简通用)

核心规律

罗马数字整体从左到右数值递减;

  • 若当前字符值 < 右侧字符值:属于减法组合,总和减去当前值;

  • 其余情况:总和加上当前值。

步骤:

  1. 建立字符到数字的映射表;

  2. 遍历字符串到倒数第二位:

    • map[s[i]] < map[s[i+1]]sum -= map[s[i]]

    • 否则:sum += map[s[i]]

  3. 最后单独加上末尾字符的值;

优势

  • 代码短,无冗长 switch 分支;

  • 统一一套判断逻辑,不用分七种字符单独处理特殊情况;

  • 时间 O (n ),空间 O(1)(固定 7 个映射)。

3. 我的可优化代码(逻辑存在多处 bug,思路繁琐)

复制代码
class Solution {
public:
    int romanToInt(string s) {
        int sum = 0;
        for(int i=0;i<s.length();i++){
            switch(s[i]){
                case 'I':
                    if(s[i+1]=='V'||s[i+1]=='X')
                        break;
                    sum++;
                    break;
                case 'V':
                    if(i>0 && s[i-1]=='I'){
                        sum+=4;
                        break;
                    }
                    sum+=5;
                    break;
                case 'X':
                    if(i>0 && s[i-1]=='I'){
                        sum+=9;
                        break;
                    }
                    if(s[i+1]=='L'||s[i+1]=='C')
                        break;
                    sum+=10;
                    break;
                case 'L':
                    if(i>0 && s[i-1]=='X'){
                        sum+=40;
                        break;
                    }
                    sum+=50;
                    break;
                case 'C':
                    if(i>0 && s[i-1]=='X'){
                        sum+=90;
                        break;
                    }
                    if(s[i+1]=='D'||s[i+1]=='M')
                        break;
                    sum+=100;
                    break;
                case 'D':
                    if(i>0 && s[i-1]=='C'){
                        sum+=400;
                        break;
                    }
                    sum+=500;
                    break;
                case 'M':
                    if(i>0 && s[i-1]=='C'){
                        sum+=900;
                        break;
                    }
                    sum+=1000;
                    break;
                default:
                    break;
            }
        }
        return sum;
    }
};

代码致命 bug

  1. 越界访问 s[i+1]

    i 走到最后一位时i+1超出字符串下标,访问非法内存,运行崩溃;

  2. 重复叠加数值

    例如IV:i=0 (I) 满足si+1=='V'直接 break,不加 1;i=1 (V) 判断前一位是 I,sum +=4,结果正确;

    但IX、XL、XC、CD、CM均会出现重复特殊值叠加逻辑,极容易算错;

  3. 分支逻辑割裂,极易漏写 / 写错特殊条件

    七种字符分开处理,每个字符单独判断左右相邻,代码冗余庞大,维护困难;

  4. 错误判断:X 分支判断 s[i-1]=='I' 求 9,逻辑写反,IX 是 I 在前 X 在后,该判断永远不会触发,IX 计算直接出错。

整体缺陷

  • 靠 switch 暴力分情况,代码冗长、边界越界、逻辑易出错;

  • 没有统一的数学判断规则,靠人工枚举所有减法组合,扩展性差。

4. 最优标准代码(哈希映射,统一判断)

复制代码
#include <unordered_map>
#include <string>
using namespace std;
class Solution {
public:
    int romanToInt(string s) {
        unordered_map<char, int> mp = {
            {'I',1},{'V',5},{'X',10},{'L',50},
            {'C',100},{'D',500},{'M',1000}
        };
        int sum = 0;
        int n = s.size();
        for(int i = 0; i < n - 1; i++){
            if(mp[s[i]] < mp[s[i+1]]){
                sum -= mp[s[i]];
            }else{
                sum += mp[s[i]];
            }
        }
        // 最后一位一定只加不减
        sum += mp[s.back()];
        return sum;
    }
};

5. 总结

  1. 你的 switch 暴力分支写法存在数组越界、逻辑判断错误,无法正常通过用例,不推荐;

  2. 通用核心规则:前小后大则减当前值,否则加当前值,一套逻辑覆盖所有情况;

  3. 遍历只到倒数第二位,避免访问 i+1 越界,末尾字符单独累加;

  4. 用哈希表存储字符数值映射,消除大量重复 if/switch 分支,代码简洁易读。

6. 相关知识拓展

拓展 1:反向遍历简化写法

从后往前遍历,记录最大值,当前值小于最大值则相减,否则更新最大值并相加:

复制代码
int romanToInt(string s) {
    unordered_map<char,int> mp={{'I',1},{'V',5},{'X',10},{'L',50},{'C',100},{'D',500},{'M',1000}};
    int sum=0,maxVal=0;
    for(int i=s.size()-1;i>=0;i--){
        int cur=mp[s[i]];
        if(cur<maxVal) sum-=cur;
        else {
            sum+=cur;
            maxVal=cur;
        }
    }
    return sum;
}

拓展 2:进阶题目 LC12 整数转罗马数字

逆向转换,采用贪心,从大到小匹配数值符号对;

拓展 3:复杂度对比

  • switch 暴力分支:代码冗余,存在越界 bug,理论时间 O (n);

  • 哈希正向遍历最优解:时间 O (n),空间 O (1)(固定 7 组映射);

  • 反向遍历:时间 O (n),空间 O (1)。

拓展 4:易错点记忆

  1. 正向遍历不要访问 s[n],循环上限 n-1

  2. 减法组合本质:小数出现在大数左侧,统一做减法,无需单独枚举 IV、IX 等所有特例。

相关推荐
深圳雨林凯AI3 小时前
图案裂变的“风格归一化“设计思路:主体保得住,画风才拉得齐|雨林凯AI技术笔记
人工智能·笔记
北域码匠3 小时前
高通滤波算法深度解析(High-Pass Filter)
stm32·算法·c#·数字信号处理·嵌入式开发·滤波算法·高通滤波
指掀涛澜天下惊4 小时前
强化学习进阶篇八 策略梯度算法
深度学习·学习·算法·强化学习
乌萨奇也要立志学C++4 小时前
【洛谷】kmp算法
开发语言·算法
禹凕4 小时前
Dijkstra算法详解与应用
python·算法
冬木家居5 小时前
40㎡客厅变形记,小家住出大自由[特殊字符]
android·经验分享·笔记·智能家居·微信公众平台
YaraMemo5 小时前
元启发式算法框架
人工智能·算法·5g·信息与通信·启发式算法·信号处理
摇滚侠5 小时前
《SpringBoot 3:入门与应用实战》第 12 章 JDBC 与事务 数据库初始化机制 阅读笔记 36
数据库·spring boot·笔记
weixin_307779135 小时前
一维无粘 Burgers 方程的激波形成问题:MacCormack 格式求解
c++·算法·matlab
HugoStudio_SWAN5 小时前
洛谷 B4500 / B4449 / B3843 凯撒密码、密码强度与密码合规——加密与安全的三道门
c++·学习·程序人生·算法·安全