LeetCode 补充题 13. 中文数字转阿拉伯数字
题目描述
✅ 补充题 13. 中文数字转阿拉伯数字
题意分析
输入是一串中文数字,比如
一千零二万零三百,要求输出它对应的阿拉伯数字 10020300。这是一道典型的补充题,题面不给严格文法,需要自己从中文的读数规则里把结构提炼出来。中文数字和阿拉伯数字最大的区别在于:阿拉伯数字靠位置表达权重,第几位就是几个十的幂;中文数字靠显式的单位词表达权重,数值和单位成对出现。这意味着不能像
atoi那样「读一位乘十加一位」,必须让每个数字等到它后面的单位词出现才知道自己有多重。单位词分两个层级,这是本题全部复杂度的来源。
十、百、千是节内单位,它们与前面的数字相乘后在本节内累加;万、亿是节单位,它们作用于前面一整节的结果。正因为层级不同,三千四百里的千只管三,而三千四百万里的万要管住三千四百整体。约束透露的信号:输入长度不大,且是一遍读完就能确定语义的线性结构(没有括号、没有回溯歧义),说明单趟扫描配几个累加变量就够,不需要建语法树也不需要递归下降。
边界情况:
十单独出现或出现在开头时表示一十,前面省略了一;零只是占位符,用来提示读者中间有空缺的数位,本身不贡献数值;两在口语中等同二;数值可能超过 21 亿,比如一百二十三亿,需要考虑用更宽的整型承接。
解法:按小节和大单位分层累加
核心思路
中文数字不是按字符位置计权,而是由单位决定权重。扫描时维护三个状态:
number:刚读到、尚未绑定单位的数字;section:当前“万以内”小节已经结算的值;total:已经跨过大单位的值。遇到
十、百、千,把number × unit加入section;单位前省略数字时按1处理,例如“十二”就是“一十二”。遇到万,当前小节整体乘一万后加进total。遇到亿,它会作用于左侧已经积累的全部内容,因此执行(total + section + number) × 一亿。
亿与万的结算方式不同是题眼。例如“一亿二千万”先得到一亿,再把二千这一小节乘一万;“一万亿”则要让此前的一万整体再乘一亿。
零只占位,不直接增加结果;hasNumber用来区分“尚未读到数字”和“明确读到了零”。以下实现假设输入是合法中文数字,支持“零、两、十百千、万、亿”,结果能放入 64 位整数。
解题步骤
- 读到数字时更新
number,并标记当前存在待结算数字。- 读到
十、百、千时,若单位前没有数字就补1;随后把乘积加入section,清空待结算数字。- 读到
万时,先把个位数字并入section,再执行total += section × 10000,清空当前小节。- 读到
亿时,把左侧所有内容合并后整体乘100000000,再开始新的低位部分。- 扫描结束后返回
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. 字符串解码 | 中等 | 单位可嵌套,必须用栈保存外层未完成的节 |