目录

题目描述

✅ 补充题 13. 中文数字转阿拉伯数字

题意分析

输入是一串中文数字,比如 一千零二万零三百,要求输出它对应的阿拉伯数字 10020300。这是一道典型的补充题,题面不给严格文法,需要自己从中文的读数规则里把结构提炼出来。

中文数字和阿拉伯数字最大的区别在于:阿拉伯数字靠位置表达权重,第几位就是几个十的幂;中文数字靠显式的单位词表达权重,数值和单位成对出现。这意味着不能像 atoi 那样「读一位乘十加一位」,必须让每个数字等到它后面的单位词出现才知道自己有多重。

单位词分两个层级,这是本题全部复杂度的来源。十、百、千 是节内单位,它们与前面的数字相乘后在本节内累加;万、亿 是节单位,它们作用于前面一整节的结果。正因为层级不同,三千四百 里的 只管 ,而 三千四百万 里的 要管住 三千四百 整体。

约束透露的信号:输入长度不大,且是一遍读完就能确定语义的线性结构(没有括号、没有回溯歧义),说明单趟扫描配几个累加变量就够,不需要建语法树也不需要递归下降。

边界情况: 单独出现或出现在开头时表示 一十,前面省略了 只是占位符,用来提示读者中间有空缺的数位,本身不贡献数值; 在口语中等同 ;数值可能超过 21 亿,比如 一百二十三亿,需要考虑用更宽的整型承接。

解法:按小节和大单位分层累加

核心思路

中文数字不是按字符位置计权,而是由单位决定权重。扫描时维护三个状态:

  • number:刚读到、尚未绑定单位的数字;
  • section:当前“万以内”小节已经结算的值;
  • total:已经跨过大单位的值。

遇到 十、百、千,把 number × unit 加入 section;单位前省略数字时按 1 处理,例如“十二”就是“一十二”。遇到 ,当前小节整体乘一万后加进 total。遇到 亿,它会作用于左侧已经积累的全部内容,因此执行 (total + section + number) × 一亿

亿 的结算方式不同是题眼。例如“一亿二千万”先得到一亿,再把二千这一小节乘一万;“一万亿”则要让此前的一万整体再乘一亿。

只占位,不直接增加结果;hasNumber 用来区分“尚未读到数字”和“明确读到了零”。以下实现假设输入是合法中文数字,支持“零、两、十百千、万、亿”,结果能放入 64 位整数。

解题步骤

  1. 读到数字时更新 number,并标记当前存在待结算数字。
  2. 读到 十、百、千 时,若单位前没有数字就补 1;随后把乘积加入 section,清空待结算数字。
  3. 读到 时,先把个位数字并入 section,再执行 total += section × 10000,清空当前小节。
  4. 读到 亿 时,把左侧所有内容合并后整体乘 100000000,再开始新的低位部分。
  5. 扫描结束后返回 total + section + number

以“一千零二万零三百”为例:前半节得到 1002,遇到“万”结算为 10020000;后半节得到 300,最终为 10020300

代码实现

class Solution {
    private static final String DIGITS = "零一二三四五六七八九";

    public long chineseToNumber(String text) {
        long total = 0;
        long section = 0;
        long number = 0;
        boolean hasNumber = false;

        for (int i = 0; i < text.length(); i++) {
            char ch = text.charAt(i);
            int digit = digitValue(ch);
            if (digit >= 0) {
                number = digit;
                hasNumber = true;
            } else if (ch == '十' || ch == '百' || ch == '千') {
                if (!hasNumber) {
                    number = 1;
                }
                section += number * innerUnit(ch);
                number = 0;
                hasNumber = false;
            } else if (ch == '万') {
                section += hasNumber ? number : 0;
                total += section * 10_000L;
                section = 0;
                number = 0;
                hasNumber = false;
            } else if (ch == '亿') {
                section += hasNumber ? number : 0;
                total = (total + section) * 100_000_000L;
                section = 0;
                number = 0;
                hasNumber = false;
            }
        }
        return total + section + (hasNumber ? number : 0);
    }

    private int digitValue(char ch) {
        if (ch == '两') {
            return 2;
        }
        return DIGITS.indexOf(ch);
    }

    private long innerUnit(char ch) {
        if (ch == '十') {
            return 10;
        }
        if (ch == '百') {
            return 100;
        }
        return 1000;
    }
}
var chineseDigits = map[rune]int64{
    '零': 0, '一': 1, '二': 2, '两': 2, '三': 3, '四': 4,
    '五': 5, '六': 6, '七': 7, '八': 8, '九': 9,
}

func chineseToNumber(text string) int64 {
    var total, section, number int64
    hasNumber := false

    for _, ch := range text {
        if digit, ok := chineseDigits[ch]; ok {
            number = digit
            hasNumber = true
        } else if ch == '十' || ch == '百' || ch == '千' {
            if !hasNumber {
                number = 1
            }
            section += number * innerUnit(ch)
            number = 0
            hasNumber = false
        } else if ch == '万' {
            if hasNumber {
                section += number
            }
            total += section * 10_000
            section, number, hasNumber = 0, 0, false
        } else if ch == '亿' {
            if hasNumber {
                section += number
            }
            total = (total + section) * 100_000_000
            section, number, hasNumber = 0, 0, false
        }
    }

    if hasNumber {
        section += number
    }
    return total + section
}

func innerUnit(ch rune) int64 {
    if ch == '十' {
        return 10
    }
    if ch == '百' {
        return 100
    }
    return 1000
}

复杂度分析

  • 时间复杂度:$O(n)$,每个中文字符只处理一次。
  • 空间复杂度:$O(1)$,数字映射表和状态变量大小固定。

关键点总结

  • 状态要与中文单位层级对应:裸数字、万以内小节、跨小节总值。
  • 十百千 只结算前面的一个数字, 结算当前小节,亿 结算左侧整体。
  • 大单位出现时要带上尚未进入 section 的个位数字。
  • 用 64 位整数承接结果,避免跨“亿”后溢出。
  • 补充题题面常不完整,面试时应先确认合法字符、最大单位和是否支持“两”。

易错点总结

  • “万”处只计算 section,漏掉末尾 number:如“一千零二万”会少两万。
  • 结算大单位后没有清空 section:上一小节会被重复计算。
  • number == 0 判断是否省略“一”:无法区分没有数字和明确的“零”,应单独保存 hasNumber
  • 把“亿”也写成 total += section × unit:无法正确处理“一万亿”这类嵌套大单位。
  • 使用 32 位 int:如“一百二十三亿”会溢出。

相似题目

题目 难度 考察点
13. 罗马数字转整数 简单 同为符号转数值,但权重靠「小在大前则减」而非单位词决定
12. 整数转罗马数字 中等 反方向输出,用贪心按权重表从大到小减
273. 整数转换英文表示 困难 本题的逆过程,英文按三位一节而中文按四位一节
8. 字符串转换整数 (atoi) 中等 纯位置权重解析,难点转移到符号、空格与溢出截断
65. 有效数字 困难 只判合法性不求值,考状态机而非累加器
227. 基本计算器 II 中等 同样是「遇到低优先级符号才结算已积累的中间结果」
面试题 16.26. 计算器 中等 结算时机相同,用栈保存待加项而非单个 total 变量
394. 字符串解码 中等 单位可嵌套,必须用栈保存外层未完成的节