题目描述

✅ 面试题 16.08. 整数的英语表示

image-20260929011423806

题意分析

将非负 32 位整数转换为英文表示。单词间只保留一个空格,不加 and;整个数字为零时返回 Zero,其他数字内部的零位或零分组不单独读出。

英文数量级每三位递进一次,从高到低为 Billion、Million、Thousand 和没有单位的个位组。利用这个结构,可以把整个转换拆成“确定各组数量级”和“转换三位以内的数”两部分。

不能简单逐位翻译,因为 10 到 19 有独立词形,整十也有专门名称。将这些固定词形放进查找表后,每个三位组只需拆成百位、十位与个位。

解法:三位一组递归转换

核心思路

[!blue]

外层的 num 表示尚未转换的低位部分。按 10^9、10^6、10^3、1 依次处理:用除法取出当前组 value,转换后追加对应单位,再用取模去掉这一组。每处理完一个数量级,剩余值都小于它,下一个组便不会超过三位;最高的十亿组在本题范围内也只有一位。

当前组为零时直接跳过,不输出组内容或单位。此时 num 本来就小于当前数量级,所以不执行取模也不会改变余下部分。外层只处理非零组,唯一会被全部跳过的整体零由入口单独返回 Zero。

convert 与 Go 的 convertNumber 只处理 0..999。输入为零时返回空串;小于 20 时直接查特殊词形表;小于 100 时读取整十名称,再转换个位余数;其余情况先读取百位数字和 Hundred,再转换余下两位。每次递归只保留更小的余数,最终一定落到查表或零的分支。

组内的零返回空串,表示后面已经没有要读的部分,而不是再补一个 Zero。Java 用组内的 trim 去掉空余数留下的尾空格,外层 appendPart 只在两个非空片段间加一个空格;Go 则只收集非空片段,用 strings.Join 统一连接。

每个非零三位组都被准确转换,并且按从高到低的顺序附上对应数量级;零组不贡献文字,因此拼接结果既保持原数的数值结构,也符合题目的空格格式。

解题步骤

  • num == 0 时直接返回 Zero。
  • 按 {10^9, 10^6, 10^3, 1} 从高位取出当前三位组 value = num / VALUES[i]。
  • value != 0 时,将它转为三位以内的英文,并追加对应的 UNITS[i];随后用取模去掉已处理部分。
  • 三位转换中依次处理 < 20、< 100、< 1000 三种情况,最后用单个空格连接所有片段。

整百、整十会产生零余数,转换函数返回空串即可终止,不会增加多余单词。中间出现零组时只跳过该组,仍继续检查更低数量级。非负 32 位整数最多用到 Billion,当前四个数量级足以覆盖全部输入。

代码实现

// 从 Billion 到个位组三位一组处理,每组只需转换 0..999。
class Solution {
    private static final String[] LESS_THAN_20 = {
        "",
        "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[] VALUES = {
        1000000000,
        1000000,
        1000,
        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 < VALUES.length; i++) {
            int value = num / VALUES[i];

            if (value == 0) {
                continue;
            }

            appendPart(answer, convert(value));
            appendPart(answer, UNITS[i]);
            num %= VALUES[i];
        }

        return answer.toString().trim();
    }

    private String convert(int num) {
        if (num == 0) {
            return "";
        }

        if (num < 20) {
            return LESS_THAN_20[num];
        }

        if (num < 100) {
            return (TENS[num / 10] + " " + convert(num % 10)).trim();
        }

        return (LESS_THAN_20[num / 100] + " Hundred " + convert(num % 100)).trim();
    }

    private void appendPart(StringBuilder builder, String part) {
        if (part.isEmpty()) {
            return;
        }

        if (builder.length() > 0) {
            builder.append(' ');
        }

        builder.append(part);
    }
}
import "strings"

// 从 Billion 到个位组三位一组处理,每组只需转换 0..999。
func numberToWords(num int) string {
    if num == 0 {
        return "Zero"
    }

    values := []int{
        1000000000,
        1000000,
        1000,
        1,
    }
    units := []string{
        "Billion",
        "Million",
        "Thousand",
        "",
    }
    parts := []string{}

    for i, value := range values {
        cur := num / value
        if cur == 0 {
            continue
        }
        parts = append(parts, convertNumber(cur))
        if units[i] != "" {
            parts = append(parts, units[i])
        }
        num %= value
    }

    return strings.Join(parts, " ")
}

func convertNumber(num int) string {
    lessThan20 := []string{
        "",
        "One",
        "Two",
        "Three",
        "Four",
        "Five",
        "Six",
        "Seven",
        "Eight",
        "Nine",
        "Ten",
        "Eleven",
        "Twelve",
        "Thirteen",
        "Fourteen",
        "Fifteen",
        "Sixteen",
        "Seventeen",
        "Eighteen",
        "Nineteen",
    }
    tens := []string{
        "",
        "",
        "Twenty",
        "Thirty",
        "Forty",
        "Fifty",
        "Sixty",
        "Seventy",
        "Eighty",
        "Ninety",
    }

    if num == 0 {
        return ""
    }
    if num < 20 {
        return lessThan20[num]
    }
    if num < 100 {
        parts := []string{
            tens[num/10],
        }
        if num%10 != 0 {
            parts = append(parts, convertNumber(num%10))
        }
        return strings.Join(parts, " ")
    }

    parts := []string{
        lessThan20[num/100],
        "Hundred",
    }
    if num%100 != 0 {
        parts = append(parts, convertNumber(num%100))
    }
    return strings.Join(parts, " ")
}

复杂度分析

  • 时间复杂度:$O(1)$,本题固定为 32 位整数,至多处理四个三位组,每组递归与输出长度均有常数上界。
  • 空间复杂度:$O(1)$,查找表、分组数、递归深度以及结果长度都受固定整数范围限制。

关键点总结

[!green]

  • 三位分组是结构核心:外层负责数量级,内层只负责 $0$ 到 $999$ 的读法。
  • 0 有两种语义:整个数字为零时输出 Zero,分组内部为零时输出空串并跳过该组。
  • $10$ 到 $19$ 必须单独查表,不能套用整十位规则。
  • Java 跳过空片段并在追加前补分隔空格,Go 收集非空片段后连接,两者都保证单词间只有一个空格。

易错点总结

[!yellow]

  • 为零分组输出 Zero:只有整个输入为零才读作 Zero,非零数字内部的零组应跳过。
  • 按整十加个位拼出 10 到 19:这一范围使用独立词形,必须直接查表。
  • 忘记处理整体为零:通用分组逻辑会得到空串,所以 num == 0 必须单独返回 Zero。
  • 边拼接边无条件加空格:容易出现连续空格或尾空格;应只追加非空片段并统一连接。

相似题目

题目 难度 关联与区别
12. 整数转罗马数字 中等 同样按数值区段输出规范表示,原题使用罗马数字,本题按三位数量级和英语词形转换。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2026/92307864
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!