LeetCode 补充题 13. 中文数字转阿拉伯数字
题目描述
给定合法的中文整数字符串
text,请将其转换为对应的非负整数并返回。采用带数位单位的中文读法:数字包括“零”到“九”及“两”,小单位包括“十、百、千”,大单位使用“万、亿”及其组合。这里处理的是数值读法,不是将中文编号逐字替换后拼接。
示例 1:
输入:text = "十二"
输出:12
示例 2:
输入:text = "一亿零一万零一"
输出:100010001
提示:
- 输入是合法的非负中文整数读法,不包含负号、小数或财务大写。
- 结果在有符号 64 位整数的可表示范围内。
- “零”表示缺失数位;小节开头省略的“一”按单位含义理解。
题意分析
将按中文数字和单位书写的非负整数转换为数值。本文沿用合法输入、结果可由有符号 64 位整数表示的约定,数字支持“零”到“九”以及“两”,小单位为“十、百、千”,大单位由“万、亿”构成。
采用按四位分节的读数方式,需要区分“万、亿、万亿、亿亿”等不同层级。数字前面的“零”表示缺失数位,不是一个乘法单位;小节开头省略的“一”按单位读法补上。这里处理按单位读出的数值,不是把逐字编号中的中文数字直接拼接成十进制字符串。
解法:按小节和大单位分层累加
核心思路
[!blue]
中文读数由不同大小的单位分层组成。对当前最高的大单位,整体都可以拆成“左侧系数 × 这个单位 + 右侧余数”。左侧系数与右侧余数还可能包含较小单位,因此继续按更低层级递归解析,直到只剩一个由十、百、千构成的小节。
大单位按“亿亿、万亿、亿、万”从高到低检查,数值分别为
10¹⁶、10¹²、10⁸、10⁴。必须先识别组合单位,否则会把“万亿”拆成两个独立结算事件。找到当前单位后,左右部分都交给下一层处理;没有该单位则直接进入下一层。这样高位与低位的作用范围始终明确:已经乘过“万亿”的高位,不会因为右侧又出现“亿”而被再次放大。对于大单位左侧仍含较小单位的写法,左侧递归会先求出完整系数,再参与一次乘法。
最内层的小节使用
section累加已经结算的位,number保存最近读到、尚未遇到单位的数字。遇到十、百、千时,将number × 单位加入小节;若这一单位前没有写数字,系数按1处理。用hasNumber区分没有数字与明确读到了零,不能只看number == 0。小节末尾可能还有未带单位的个位,要在结束时并入。各层再按乘法与加法组合结果,就得到完整数值;所有项均非负,合法结果处于 64 位范围内时,这些分层中间结果也不会超过最终值。
解题步骤
- 从最高组合单位开始,依次检查“亿亿、万亿、亿、万”。
- 当前文本没有这一单位,就交给下一层;若存在,分成单位左侧的系数文本与右侧的余数文本。
- 用下一层分别解析左右两部分,返回
左侧数值 × 当前单位值 + 右侧数值。- 所有大单位都处理完后,逐字符解析十、百、千小节:数字先暂存,遇到小单位再结算乘积。
- 小节结束时加入尚未结算的个位,空片段贡献
0,各层返回后得到最终值。
代码实现
class Solution {
private static final String DIGITS = "零一二三四五六七八九";
private static final String[] BIG_UNITS = {
"亿亿",
"万亿",
"亿",
"万"
};
private static final long[] BIG_VALUES = {
10_000_000_000_000_000L,
1_000_000_000_000L,
100_000_000L,
10_000L
};
public long chineseToNumber(String text) {
return parse(text, 0);
}
private long parse(String text, int level) {
if (level == BIG_UNITS.length) {
return parseSection(text);
}
String unit = BIG_UNITS[level];
int index = text.indexOf(unit);
if (index < 0) {
return parse(text, level + 1);
}
long coefficient = parse(text.substring(0, index), level + 1);
long remainder = parse(text.substring(index + unit.length()), level + 1);
return coefficient * BIG_VALUES[level] + remainder;
}
private long parseSection(String text) {
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 == '千') {
section += (hasNumber ? number : 1) * innerUnit(ch);
number = 0;
hasNumber = false;
}
}
return 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;
}
}
import "strings"
var chineseDigits = map[rune]int64{
'零': 0,
'一': 1,
'二': 2,
'两': 2,
'三': 3,
'四': 4,
'五': 5,
'六': 6,
'七': 7,
'八': 8,
'九': 9,
}
var bigUnits = [...]string{
"亿亿",
"万亿",
"亿",
"万",
}
var bigValues = [...]int64{
10_000_000_000_000_000,
1_000_000_000_000,
100_000_000,
10_000,
}
func chineseToNumber(text string) int64 {
return parseChinese(text, 0)
}
func parseChinese(text string, level int) int64 {
if level == len(bigUnits) {
return parseSection(text)
}
unit := bigUnits[level]
index := strings.Index(text, unit)
if index < 0 {
return parseChinese(text, level+1)
}
coefficient := parseChinese(text[:index], level+1)
remainder := parseChinese(text[index+len(unit):], level+1)
return coefficient*bigValues[level] + remainder
}
func parseSection(text string) int64 {
var 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
}
}
if hasNumber {
section += number
}
return section
}
func innerUnit(ch rune) int64 {
if ch == '十' {
return 10
}
if ch == '百' {
return 100
}
return 1000
}
复杂度分析
设输入字符数量为
n。大单位层级固定为四层,每层对分开的文本进行查找,最内层再扫描小节。
- 时间复杂度:$O(n)$,同一字符至多被固定数量的层级处理。
- 空间复杂度:Java 为 $O(n)$,递归拆分时创建子字符串;Go 为 $O(1)$ 额外空间,字符串切片共享原内容,单位层数与递归深度均为常数。
关键点总结
[!green]
- 先分大单位,再算小节,每个单位只作用于自己左侧的系数。
- 组合单位优先识别,高位结果与后续较低单位分开计算,避免重复放大。
- 小单位结算乘积,末尾补入个位;零与省略系数需要用不同状态表达。
易错点总结
[!yellow]
- 每遇到“亿”就把累计总额整体乘亿,会再次放大此前已经结算的“万亿”等高位部分。
- 先查单个“亿”再识别组合单位,会拆错大数层级;应从最高组合单位向下解析。
- 在大单位左侧只取最近一个数字,会丢掉完整系数中的十、百、千或万级内容。
- 小节结束时不加入尚未消费的数字,会漏掉个位以及大单位前的系数尾数。
- 用
number == 0判断是否省略了系数,会把已经读到的零与尚未读到数字混为一谈。- Go 按字节逐个解析中文会拆散字符,小节扫描应使用
range得到 Unicode 字符。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 补充题 14. 阿拉伯数字转中文数字 | 中等 | 编码方向相反;中文解析当前支持64位,数字转中文实现限定非负32位,往返核对需选共同范围,分节与零处理规则相通。 |