「整数转罗马数字」是一道非常有代表性的规则型算法题。
它不考复杂数据结构,也不考高深数学,但却特别适合用来考察:
- 规则抽象能力
- 贪心思想的理解
- 代码设计是否清晰、优雅
这篇笔记会从“为什么这么做”开始,一步步讲清楚。
一、题目回顾
给定一个整数 num(范围是 1 <= num <= 3999),
要求将它转换成对应的罗马数字字符串。
二、罗马数字的规则整理(关键)
在动手写代码之前,必须先把规则彻底整理清楚。
1. 基本符号
I = 1
V = 5
X = 10
L = 50
C = 100
D = 500
M = 1000
2. 特殊规则(减法表示)
以下组合不能拆开写:
IV = 4
IX = 9
XL = 40
XC = 90
CD = 400
CM = 900
这一步非常重要,否则后面逻辑会变复杂。
三、核心解题思路:贪心 + 映射表
这道题最本质的思路只有一句话:
每一步,优先使用不超过 num 的最大罗马数字
这就是典型的贪心策略。
为什么贪心是对的?
因为:
- 罗马数字是从大到小组合的
- 大数字优先使用,能保证位数最少、形式唯一
- 规则是固定的,不存在“回头更优”的情况
四、把规则变成程序能用的形式
我们直接把所有可能用到的罗马数字,按从大到小顺序列出来:
int[] values = {
1000, 900, 500, 400,
100, 90, 50, 40,
10, 9, 5, 4,
1
};
String[] symbols = {
"M", "CM", "D", "CD",
"C", "XC", "L", "XL",
"X", "IX", "V", "IV",
"I"
};
这里有两个细节:
- 特殊规则(900、400、90 等)必须提前放进去
- 顺序必须是从大到小
五、完整 Java 实现
class Solution {
public String intToRoman(int num) {
int[] values = {
1000, 900, 500, 400,
100, 90, 50, 40,
10, 9, 5, 4,
1
};
String[] symbols = {
"M", "CM", "D", "CD",
"C", "XC", "L", "XL",
"X", "IX", "V", "IV",
"I"
};
StringBuilder sb = new StringBuilder();
for (int i = 0; i < values.length; i++) {
while (num >= values[i]) {
num -= values[i];
sb.append(symbols[i]);
}
}
return sb.toString();
}
}
六、用一个完整例子走一遍流程
以 num = 1994 为例:
1994 >= 1000 → M 剩余 994
994 >= 900 → CM 剩余 94
94 >= 90 → XC 剩余 4
4 >= 4 → IV 剩余 0
最终结果:
"MCMXCIV"
这个过程非常符合人类对罗马数字的理解方式。
七、为什么一定要用 StringBuilder?
在 Java 中:
String是不可变对象- 每次拼接都会创建新对象,效率低
而 StringBuilder:
- 可变
- 拼接是原地操作
- 是这类题的标准选择
这是面试中默认的最佳实践。
八、复杂度分析
时间复杂度:
O(1)
原因是罗马数字规则是固定的,最多循环 13 次。
空间复杂度:
O(1)
只使用了常量级数组和变量。
九、这道题真正考察的能力
这道题表面看是字符串题,实际上考察的是:
- 能不能把复杂规则抽象成结构化数据
- 是否具备用“贪心思想”解决问题的意识
- 代码是否清晰、易维护、无多余分支