LeetCode 273. 整数转换英文表示
题目描述
题意分析
输入是一个非负整数,要输出它的英文读法,单词之间用单个空格分隔,首尾不能有多余空格。
上界是 $2^{31} - 1$,也就是二十一亿多一点,因此需要的量级单位只有三个:千、百万、十亿。这是一个非常关键的信号——不必设计通用的进位方案,把这三个词写死即可。
英文读法的结构其实很规整:从低位起每三位一组,每组内部读法完全一样(几百几十几),组与组之间只差一个量级后缀。真正不规整的只有组内的两处:$1$ 到 $19$ 各有专名,读法与十位无关;$20$ 到 $90$ 的整十位也有专名。这两处只能查表,任何试图用规则拼出
Eleven、Forty的做法都会失败。输出格式上有几处约定要留意:数值为零的分组整个跳过,不读出量级后缀,例如一百万零一不能读成「一百万零千零一」;百位读完后若余数为零就到此为止,不能拖一个空词;整个数为零时是唯一需要输出
Zero的情形,因为其他任何情形下零分组都被跳过了。边界情形包括:输入为零;某个中间分组为零;末尾分组为零(例如整千数);以及取到上界附近的十位数,用来检验量级词表是否够用。
解法:三位分组 + 组内查表
核心思路
英文数字每三位形成一个独立小节,量级依次是
Billion、Million、Thousand和个位组。主流程只需从高位到低位取出每个非零小节,把它翻译后追加对应量级词。小节内的规则固定:
100到999:先读百位数字和Hundred;20到99:查整十词表,再处理个位;1到19:直接查表,因为Eleven、Twelve等不能按统一规则生成。从高位向低位直接构造结果,可以避免先处理低位再反转。所有单词统一通过
appendWord追加,它只在已有内容后补一个空格,从而保证没有首尾或连续空格。值为零的小节整体跳过;只有输入本身为零时返回
Zero。题目采用美式格式,不在百位后添加And。
解题步骤
- 输入为
0时直接返回Zero。- 按
10^9、10^6、10^3、1从高到低取小节。- 小节非零时,依次处理百位、整十位和
1到19。- 个位组之外,追加当前小节的量级词。
- 对剩余数字取模,继续处理下一个小节。
例如
12345被分成12 Thousand与345,分别翻译后得到Twelve Thousand Three Hundred Forty Five。
代码实现
class Solution {
private static final String[] BELOW_TWENTY = {
"", "One", "Two", "Three", "Four",
"Five", "Six", "Seven", "Eight", "Nine",
"Ten", "Eleven", "Twelve", "Thirteen", "Fourteen",
"Fifteen", "Sixteen", "Seventeen", "Eighteen", "Nineteen"
};
private static final String[] TENS = {
"", "", "Twenty", "Thirty", "Forty",
"Fifty", "Sixty", "Seventy", "Eighty", "Ninety"
};
private static final int[] SCALES = {
1_000_000_000, 1_000_000, 1_000, 1
};
private static final String[] UNITS = {
"Billion", "Million", "Thousand", ""
};
public String numberToWords(int num) {
if (num == 0) {
return "Zero";
}
StringBuilder answer = new StringBuilder();
for (int i = 0; i < SCALES.length; i++) {
int group = num / SCALES[i];
if (group != 0) {
appendBelowThousand(answer, group);
if (!UNITS[i].isEmpty()) {
appendWord(answer, UNITS[i]);
}
}
num %= SCALES[i];
}
return answer.toString();
}
private void appendBelowThousand(StringBuilder answer, int num) {
if (num >= 100) {
appendWord(answer, BELOW_TWENTY[num / 100]);
appendWord(answer, "Hundred");
num %= 100;
}
if (num >= 20) {
appendWord(answer, TENS[num / 10]);
num %= 10;
}
if (num > 0) {
appendWord(answer, BELOW_TWENTY[num]);
}
}
private void appendWord(StringBuilder answer, String word) {
if (answer.length() > 0) {
answer.append(' ');
}
answer.append(word);
}
}
import "strings"
var belowTwenty = []string{
"", "One", "Two", "Three", "Four",
"Five", "Six", "Seven", "Eight", "Nine",
"Ten", "Eleven", "Twelve", "Thirteen", "Fourteen",
"Fifteen", "Sixteen", "Seventeen", "Eighteen", "Nineteen",
}
var tensWords = []string{
"", "", "Twenty", "Thirty", "Forty",
"Fifty", "Sixty", "Seventy", "Eighty", "Ninety",
}
func numberToWords(num int) string {
if num == 0 {
return "Zero"
}
scales := []int{1_000_000_000, 1_000_000, 1_000, 1}
units := []string{"Billion", "Million", "Thousand", ""}
var answer strings.Builder
for i, scale := range scales {
group := num / scale
if group != 0 {
appendBelowThousand(&answer, group)
if units[i] != "" {
appendWord(&answer, units[i])
}
}
num %= scale
}
return answer.String()
}
func appendBelowThousand(answer *strings.Builder, num int) {
if num >= 100 {
appendWord(answer, belowTwenty[num/100])
appendWord(answer, "Hundred")
num %= 100
}
if num >= 20 {
appendWord(answer, tensWords[num/10])
num %= 10
}
if num > 0 {
appendWord(answer, belowTwenty[num])
}
}
func appendWord(answer *strings.Builder, word string) {
if answer.Len() > 0 {
answer.WriteByte(' ')
}
answer.WriteString(word)
}
复杂度分析
- 时间复杂度:$O(d)$,$d$ 为十进制位数;32 位输入最多四个三位小节,因此本题中是 $O(1)$。
- 空间复杂度:除输出字符串外为 $O(1)$,词表和量级表大小固定;若计入输出,则为 $O(d)$。
关键点总结
- 英文读数的封闭单元是三位,组间只需添加固定量级词。
1到19与整十必须查表,规则拼接无法生成所有正确拼写。- 零小节不输出任何内容,整个数字为零才输出
Zero。- 统一的单词追加函数能从根源上避免空格格式问题。
- 先明确采用美式还是英式读法;本题不输出
And。
易错点总结
- 把
10到19拆成十位和个位:会得到不存在的英文组合。- 零小节仍追加量级词:
1000000会多出Thousand。- 从低位取组却直接追加:会把高低位顺序颠倒。
- 忘记
Billion:输入上界接近2.15billion。- 手工在每个词后加空格:容易产生首尾空格,判题会严格比较字符串。
相似题目
| 题目 | 难度 | 考察点 |
|---|---|---|
| 面试题 16.08. 整数的英语表示 | 困难 | 与本题同一问题的另一入口,可用来复核实现是否完全一致 |
| 补充题 14. 阿拉伯数字转中文数字 | 中等 | 换成中文读法,分组单位是四位而非三位,还要处理「零」的插入规则 |
| 字节面试题-阿拉伯数字转中文 | 中等 | 同为中文读法但更侧重「一十」与「十」等口语化省略的取舍 |
| 12. 整数转罗马数字 | 中等 | 同为数值到符号串的转换,但靠贪心逐个匹配面值表,没有分组结构 |
| 168. Excel 表列名称 | 简单 | 本质是从一开始计数的二十六进制转换,难点在进位时的减一修正 |
| 166. 分数到小数 | 中等 | 同为格式化输出题,核心是用哈希记录余数以识别循环节 |