LeetCode 12. 整数转罗马数字:从逐位实现到规则复用优化

罗马数字作为古老的计数体系,其转换规则围绕「加法优先、特殊减法补充」设计,LeetCode 第 12 题要求我们将 1~3999 范围内的整数转换为罗马数字,核心是精准适配罗马数字的符号规则。本文将从基础逐位实现入手,再到规则复用优化,逐步拆解两种实现思路的逻辑的逻辑,同时点明关键易错点。

一、题目核心规则梳理

首先明确罗马数字的符号与转换规则,避免实现时偏离需求:

1. 基础符号映射

符号 符号
I 1 L 50
V 5 C 100
X 10 D 500
M 1000 - -

2. 转换核心规则

  • 特殊减法规则:仅支持 4(IV)、9(IX)、40(XL)、90(XC)、400(CD)、900(CM) 六种减法形式,对应值以 4 或 9 开头的场景。

  • 加法规则:非 4/9 开头的值,优先选最大可减符号累加,且 I、X、C、M(10 的次方)最多连续累加 3 次,V、L、D 不可连续累加。

  • 范围限制:输入整数满足 1 ≤ num ≤ 3999,千位最大值为 3,无 5000 及以上符号。

二、基础实现:逐位拆解法(intToRoman_1)

这种思路的核心是「将整数按千、百、十、个位拆分,逐位适配罗马数字规则」,逻辑直观,贴合规则理解,适合新手入门。

1. 实现逻辑拆解

步骤 1:拆分每一位数值

通过取余和整除运算,分别提取千、百、十、个位的数值,注意用 Math.floor 确保结果为整数(避免浮点数干扰):

typescript 复制代码
const oneNum = Math.floor(num % 10); // 个位
const tenNum = Math.floor((num % 100) / 10); // 十位
const hundredNum = Math.floor((num % 1000) / 100); // 百位
const thousandNum = Math.floor(num / 1000); // 千位
步骤 2:逐位转换拼接

从高位到低位(千 → 个)转换,每一位遵循「先判断特殊减法,再处理加法」的逻辑:

  • 千位:仅需累加 M(因最大值为 3),无需处理减法和 5 倍符号。

  • 百位/十位/个位:先判断是否为 9(对应 CM/XC/IX),再判断是否为 4(对应 CD/XL/IV),接着判断是否 ≥5(对应 D/L/V 加后续累加),最后累加基础符号(C/X/I)。

2. 代码优势与不足

优势

逻辑直白,完全贴合罗马数字规则,每一步转换都可对应到具体规则,调试和理解成本低,适合快速上手实现。

不足

代码冗余严重:百位、十位、个位的转换逻辑高度重复,仅符号不同,后续维护需修改多处代码,扩展性差。

三、优化实现:规则复用版(intToRoman_2)

针对基础版的冗余问题,核心优化思路是「抽象统一规则,复用处理逻辑」,将重复的逐位判断抽象为规则表,用一次循环完成所有位的转换。

1. 实现逻辑拆解

步骤 1:定义统一规则表

将每一位的「基础符号、5 倍符号、9 倍符号、4 倍符号、位权」封装为对象,按「个 → 千」顺序排列,后续从高位到低位遍历:

typescript 复制代码
const rules = [
  { ones: 'I', fives: 'V', tens: 'IX', four: 'IV', divisor: 1 },    // 个位
  { ones: 'X', fives: 'L', tens: 'XC', four: 'XL', divisor: 10 },   // 十位
  { ones: 'C', fives: 'D', tens: 'CM', four: 'CD', divisor: 100 },  // 百位
  { ones: 'M', fives: '', tens: '', four: '', divisor: 1000 },      // 千位
];

说明:千位无 5 倍(5000)、9 倍(9000)、4 倍(4000)符号,故对应字段设为空,适配范围限制。

步骤 2:遍历规则表复用逻辑

从千位到个位遍历规则表,计算当前位数值后,复用同一套转换逻辑:

typescript 复制代码
// 从高位(千位)到低位(个位)遍历
for (let i = rules.length - 1; i >= 0; i--) {
  const { ones, fives, tens, four, divisor } = rules[i];
  const digit = Math.floor((num % (divisor * 10)) / divisor); // 计算当前位数值
  if (digit === 0) continue; // 位值为 0 跳过,无需拼接符号

  let d = digit;
  while (d > 0) {
    if (d === 9) { res += tens; d -= 9; }
    else if (d === 4) { res += four; d -= 4; }
    else if (d >= 5) { res += fives; d -= 5; }
    else { res += ones; d--; }
  }
}

2. 核心优化点

  • 消除冗余:将 3 段重复逻辑合并为 1 段,修改符号仅需调整规则表,维护成本大幅降低。

  • 适配范围:千位规则字段为空,自然跳过 4/5/9 的判断,符合 1~3999 的输入限制。

  • 逻辑统一:无论哪一位,都遵循「特殊减法优先,加法补充」的规则,一致性更强。

四、关键易错点解析

1. 数据类型陷阱

基础版中若用 toFixed(0) 替代 Math.floor,会返回字符串类型的数值,导致后续比较(如 i === 9)失效。务必用 Math.floor 确保数值类型正确。

2. 千位无需处理特殊情况

因输入最大为 3999,千位数值仅为 0~3,永远不会触发 d=4、d=9、d≥5 的判断,规则表中千位对应字段设为空即可,无需额外逻辑。

3. 高位到低位转换顺序

罗马数字需从高位到低位拼接,因此规则表遍历需从千位(rules.length - 1)开始,若顺序颠倒会导致结果错乱(如 1994 变成 IVXCM 等错误形式)。

五、测试用例验证

针对核心场景测试两种实现,确保结果正确:

typescript 复制代码
// 基础测试
console.log(intToRoman(3));      // III(纯加法)
console.log(intToRoman(4));      // IV(减法形式)
console.log(intToRoman(9));      // IX(减法形式)
console.log(intToRoman(58));     // LVIII(50+5+3)
// 复杂测试
console.log(intToRoman(1994));   // MCMXCIV(1000+900+90+4)
console.log(intToRoman(3999));   // MMMCMXCIX(3000+900+90+9)

两种实现均能通过以上测试,输出符合预期的罗马数字。

六、总结与选择建议

实现对比

实现方式 优点 缺点 适用场景
逐位拆解法 逻辑直观、调试简单 代码冗余、扩展性差 新手入门、快速验证逻辑
规则复用版 代码简洁、可维护性强 需理解规则抽象逻辑 实际开发、算法优化场景

核心思路提炼

罗马数字转换的本质是「按位匹配规则」,优化的关键在于识别重复逻辑并抽象封装。规则复用版通过将「符号与位权」绑定,实现了逻辑的统一,这也是算法优化中「抽象复用」思想的典型应用。

无论是哪种实现,都需紧扣罗马数字的三条核心规则,尤其注意特殊减法和符号累加限制,才能确保转换结果准确无误。

相关推荐
绿算技术2 小时前
重塑智算存储范式:绿算技术NVMe-oF芯片解决方案全景剖析
人工智能·算法·gpu算力
方安乐2 小时前
react笔记之useMemo
前端·笔记·react.js
sin_hielo2 小时前
leetcode 3510
数据结构·算法·leetcode
清风细雨_林木木2 小时前
react 中 form表单提示
前端·react.js·前端框架
小二·2 小时前
Python Web 开发进阶实战:边缘智能网关 —— 在 Flask + MicroPython 中构建轻量级 IoT 边缘推理平台
前端·python·flask
TOPGUS2 小时前
解析200万次对话数据:ChatGPT引用内容的核心特征与优化策略
前端·人工智能·搜索引擎·chatgpt·seo·数字营销
Anastasiozzzz2 小时前
力扣hot100 20.有效的括号 解析
java·算法·面试·力扣
CrazyClaz2 小时前
负载均衡算法
算法·负载均衡
羊仔AI探索2 小时前
前端已死,未来已来,谷歌Gemini 3 Pro杀回来了!
前端·人工智能·ai·aigc