目录

题目描述

273. 整数转换英文表示

题意分析

输入是一个非负整数,要输出它的英文读法,单词之间用单个空格分隔,首尾不能有多余空格。

上界是 $2^{31} - 1$,也就是二十一亿多一点,因此需要的量级单位只有三个:千、百万、十亿。这是一个非常关键的信号——不必设计通用的进位方案,把这三个词写死即可。

英文读法的结构其实很规整:从低位起每三位一组,每组内部读法完全一样(几百几十几),组与组之间只差一个量级后缀。真正不规整的只有组内的两处:$1$ 到 $19$ 各有专名,读法与十位无关;$20$ 到 $90$ 的整十位也有专名。这两处只能查表,任何试图用规则拼出 ElevenForty 的做法都会失败。

输出格式上有几处约定要留意:数值为零的分组整个跳过,不读出量级后缀,例如一百万零一不能读成「一百万零千零一」;百位读完后若余数为零就到此为止,不能拖一个空词;整个数为零时是唯一需要输出 Zero 的情形,因为其他任何情形下零分组都被跳过了。

边界情形包括:输入为零;某个中间分组为零;末尾分组为零(例如整千数);以及取到上界附近的十位数,用来检验量级词表是否够用。

解法:三位分组 + 组内查表

核心思路

英文数字每三位形成一个独立小节,量级依次是 BillionMillionThousand 和个位组。主流程只需从高位到低位取出每个非零小节,把它翻译后追加对应量级词。

小节内的规则固定:

  • 100999:先读百位数字和 Hundred
  • 2099:查整十词表,再处理个位;
  • 119:直接查表,因为 ElevenTwelve 等不能按统一规则生成。

从高位向低位直接构造结果,可以避免先处理低位再反转。所有单词统一通过 appendWord 追加,它只在已有内容后补一个空格,从而保证没有首尾或连续空格。

值为零的小节整体跳过;只有输入本身为零时返回 Zero。题目采用美式格式,不在百位后添加 And

解题步骤

  1. 输入为 0 时直接返回 Zero
  2. 10^9、10^6、10^3、1 从高到低取小节。
  3. 小节非零时,依次处理百位、整十位和 119
  4. 个位组之外,追加当前小节的量级词。
  5. 对剩余数字取模,继续处理下一个小节。

例如 12345 被分成 12 Thousand345,分别翻译后得到 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)$。

关键点总结

  • 英文读数的封闭单元是三位,组间只需添加固定量级词。
  • 119 与整十必须查表,规则拼接无法生成所有正确拼写。
  • 零小节不输出任何内容,整个数字为零才输出 Zero
  • 统一的单词追加函数能从根源上避免空格格式问题。
  • 先明确采用美式还是英式读法;本题不输出 And

易错点总结

  • 1019 拆成十位和个位:会得到不存在的英文组合。
  • 零小节仍追加量级词:1000000 会多出 Thousand
  • 从低位取组却直接追加:会把高低位顺序颠倒。
  • 忘记 Billion:输入上界接近 2.15 billion。
  • 手工在每个词后加空格:容易产生首尾空格,判题会严格比较字符串。

相似题目

题目 难度 考察点
面试题 16.08. 整数的英语表示 困难 与本题同一问题的另一入口,可用来复核实现是否完全一致
补充题 14. 阿拉伯数字转中文数字 中等 换成中文读法,分组单位是四位而非三位,还要处理「零」的插入规则
字节面试题-阿拉伯数字转中文 中等 同为中文读法但更侧重「一十」与「十」等口语化省略的取舍
12. 整数转罗马数字 中等 同为数值到符号串的转换,但靠贪心逐个匹配面值表,没有分组结构
168. Excel 表列名称 简单 本质是从一开始计数的二十六进制转换,难点在进位时的减一修正
166. 分数到小数 中等 同为格式化输出题,核心是用哈希记录余数以识别循环节