题目描述

给定合法的中文整数字符串 text,请将其转换为对应的非负整数并返回。

采用带数位单位的中文读法:数字包括“零”到“九”及“两”,小单位包括“十、百、千”,大单位使用“万、亿”及其组合。这里处理的是数值读法,不是将中文编号逐字替换后拼接。

示例 1:

输入:text = "十二"
输出:12

示例 2:

输入:text = "一亿零一万零一"
输出:100010001

提示:

  • 输入是合法的非负中文整数读法,不包含负号、小数或财务大写。
  • 结果在有符号 64 位整数的可表示范围内。
  • “零”表示缺失数位;小节开头省略的“一”按单位含义理解。

题意分析

将按中文数字和单位书写的非负整数转换为数值。本文沿用合法输入、结果可由有符号 64 位整数表示的约定,数字支持“零”到“九”以及“两”,小单位为“十、百、千”,大单位由“万、亿”构成。

采用按四位分节的读数方式,需要区分“万、亿、万亿、亿亿”等不同层级。数字前面的“零”表示缺失数位,不是一个乘法单位;小节开头省略的“一”按单位读法补上。这里处理按单位读出的数值,不是把逐字编号中的中文数字直接拼接成十进制字符串。

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

核心思路

[!blue]

中文读数由不同大小的单位分层组成。对当前最高的大单位,整体都可以拆成“左侧系数 × 这个单位 + 右侧余数”。左侧系数与右侧余数还可能包含较小单位,因此继续按更低层级递归解析,直到只剩一个由十、百、千构成的小节。

大单位按“亿亿、万亿、亿、万”从高到低检查,数值分别为 10¹⁶、10¹²、10⁸、10⁴。必须先识别组合单位,否则会把“万亿”拆成两个独立结算事件。找到当前单位后,左右部分都交给下一层处理;没有该单位则直接进入下一层。

这样高位与低位的作用范围始终明确:已经乘过“万亿”的高位,不会因为右侧又出现“亿”而被再次放大。对于大单位左侧仍含较小单位的写法,左侧递归会先求出完整系数,再参与一次乘法。

最内层的小节使用 section 累加已经结算的位,number 保存最近读到、尚未遇到单位的数字。遇到十、百、千时,将 number × 单位 加入小节;若这一单位前没有写数字,系数按 1 处理。用 hasNumber 区分没有数字与明确读到了零,不能只看 number == 0。

小节末尾可能还有未带单位的个位,要在结束时并入。各层再按乘法与加法组合结果,就得到完整数值;所有项均非负,合法结果处于 64 位范围内时,这些分层中间结果也不会超过最终值。

解题步骤

  1. 从最高组合单位开始,依次检查“亿亿、万亿、亿、万”。
  2. 当前文本没有这一单位,就交给下一层;若存在,分成单位左侧的系数文本与右侧的余数文本。
  3. 用下一层分别解析左右两部分,返回 左侧数值 × 当前单位值 + 右侧数值。
  4. 所有大单位都处理完后,逐字符解析十、百、千小节:数字先暂存,遇到小单位再结算乘积。
  5. 小节结束时加入尚未结算的个位,空片段贡献 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位,往返核对需选共同范围,分节与零处理规则相通。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2026/40605500
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!