从一读到一亿需要读多少个汉字?

前言

刷某乎突然刷到了这个问题,正好自己最近也在写LeetCode,想着这题也不算难,正好可以拿来练手。话不多说,先看题目

题目描述

从一读到一亿需要读多少个汉字,而汉字在不同的场合、环境以及不同的人口中可能存在细微的差异,为了统一读法,以保证本文的答案与大多数答案没有出入,现对如何读出一个一亿一下的数有如下定义:

  • 对于零在万位的情况:如果千位有数,则省略不读零
    10_1020读作十万一千零二十而不是十万[零]一千零二十
    10_0102读作十万零一百零二
  • 对于两个非零数之间存在多个零的情况:读且只读一个零
    1001_0020读作一千零一万零二十
    1000_0020读作一千万零二十
  • 对于最高位为十(万)位的数:如果十(万)位为一,则一省略不读
    10_1210读作十万一千二百一十
  • 普通情况,直接正常按位读出
    1234_5678读作一千二百三十四万五千六百七十八
    34_5078读作三十四万五千零七十八

思路分析

经过上面的总结,不难发现:万位之前与万位之后的读法相同,如:
1234_5678读作一千二百三十四万五千六百七十八
1234_0567读作一千二百三十四万零五百六十七
1234_0000读作一千二百三十四万
1234读作一千二百三十四
但是中间连接它们的有时为“万零”,有时为“万”,具体总结如下:

  • 后四位为0,连接符为“万”,且需要去掉后四位读出来的“零”
    1234_0000读作一千二百三十四万而是一千二百三十四万零
  • 后四位整体不为0,但千位为0,连接符为“万零”
    1234_0567读作一千二百三十四万零五百六十七
  • 后四位整体不为0,但千位不为0,连接符为“万”
    1234_5678读作一千二百三十四万五千六百七十八

所以如果想读出一个一亿一下的数,我们只需要将数分为前后各4位分别读出,然后加上中间的“万[零]”即可。
有了这个思路,我们已经将题目从从一读到一亿需要读多少个汉字,简化成了从一读到一万需要读多少个汉字

从一读到一万需要多少个汉字

通过观察下面几个数的读法,不难发现规律:
1234读作一千二百三十四
234读作二百三十四
34读作三十四
4读作
所以想要读一个千位数,我们只需要读千位即可,因为百位已经读过了,重复读的话一定会让我们写出的代码更复杂,那怎么体现我们已经读过这个数了呢?不难想到,我们可以使用动态规划的方式,将读过的数保存在一个数组中,每次读的时候将读过的值直接从数组拿出来,那就不用重复读了。

动态规划以及它的转移方程

顾名思义,转移方程就是一个较大的数如何转成一个较小的数的转移方法,也可以理解为一个函数:
大数abcd的汉字数 = dp(小数bcd的汉字数)
这里的dp就是转移方程。
我们只要得到转移方程,就能得出一到一万每一个数字的中文读法所需要的汉字了。

  • 从1~10为一个汉字
    dp[i] = 1
  • 从11~20为两个汉字
    dp[i] = 2
  • 从20~99,汉字个数与末尾是否为零相关
    dp[i] = i % 10 == 0 ? 2 : 3
  • 从100~999,汉字个数与个位、十位的零相关
    if (i % 100 == 0) dp[i] = 2; else if (i % 10 == 0 || i % 100 < 10) dp[i] = 4; else dp[i] = 5;
  • 从1000~9999,汉字的个数可以由之前的结果组合而来
    2000读作两千 dp[2000] = 2
    2002读作两千零二 dp[2002] = 2 + dp[2] + 1
    2020读作两千零二十 dp[2020] = 2 + dp[20] + 1
    2200读作两千二百 dp[2200] = 2 + dp[200]
    2022读作两千零二十二 dp[2022] = 2 + dp[22] + 1
    2220读作两千二百二十 dp[2220] = 2 + dp[220]
    2202读作两千二百零二 dp[2202] = 2 + dp[202]
    2222读作两千二百二十二 dp[2222] = 2 + dp[222]
    可总结为:
    dp[i] = dp[i % 1000] + (i % 1000 == 0 ? 2 : i % 1000 < 10 ? 3 : i % 1000 < 20 ? 4 : i % 1000 < 100 ? 3 : 2);

代码[从一读到一万]

public static void main(String[] args) {
    long start = System.nanoTime();
    int res = 0;
    int[] dp = new int[10000];
    for (int i = 1; i <= 10; i++) {
        dp[i] = 1;
        res += dp[i];
    }
    for (int i = 11; i <= 20; i++) {
        dp[i] = 2;
        res += dp[i];
    }
    for (int i = 21; i <= 99; i++) {
        dp[i] = i % 10 == 0 ? 2 : 3;
        res += dp[i];
    }
    for (int i = 100; i <= 999; i++) {
        dp[i] = i % 100 == 0 ? 2 : i % 10 == 0 || i % 100 < 10 ? 4 : 5;
        res += dp[i];
    }
    for (int i = 1000; i < 10000; i++) {
        dp[i] = dp[i % 1000] + (i % 1000 == 0 ? 2 : i % 1000 < 10 ? 3 : i % 1000 < 20 ? 4 : i % 1000 < 100 ? 3 : 2);
        res += dp[i];
    }
    System.out.println("从1读到10000共读了" + (res + 2) + "个汉字, 耗时" + (System.nanoTime() - start) / 1e6 + "ms");
}

// 结果
从1读到10000共读了64693个汉字, 耗时0.430422ms

从一读到一亿的转移方程

上面已经给出了从一读到一万的转移方程,而且在之前的分析中,从一读到一亿的方程可以通过从一读到一万的方程得来:

  • 后四位为0,连接符为“万”,且需要去掉后四位读出来的“零”
    dp[1234_0000] = dp[1234] + dp[0]
  • 后四位整体不为0,但千位为0,连接符为“万零”
    dp[1234_0567] = dp[1234] + dp[567] + 2
  • 后四位整体不为0,但千位不为0,连接符为“万”
    dp[1234_5678] = dp[1234] + dp[5678] + 1
  • 后四位为十几的时候记得还要读出一
    dp[1234_0018] = dp[1234] + dp[18] + 3

最终代码[从一读到一亿]

public class ReadNumbers {
    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        int res = 0;
        int[] dp = new int[1_0000];
        dp[0] = 1;
        for (int i = 1; i <= 10; i++) {
            dp[i] = 1;
            res += dp[i];
        }
        for (int i = 11; i <= 20; i++) {
            dp[i] = 2;
            res += dp[i];
        }
        for (int i = 21; i <= 99; i++) {
            dp[i] = i % 10 == 0 ? 2 : 3;
            res += dp[i];
        }
        for (int i = 100; i <= 999; i++) {
            dp[i] = i % 100 == 0 ? 2 : i % 10 == 0 || i % 100 < 10 ? 4 : 5;
            res += dp[i];
        }
        for (int i = 1000; i < 1_0000; i++) {
            int next = i % 1000;
            dp[i] = dp[next] + (next == 0 ? 1 : next < 10 ? 3 : next < 20 ? 4 : next < 100 ? 3 : 2);
            res += dp[i];
        }
        for (int i = 1_0000; i < 1_0000_0000; i++) {
            int next = i % 1_0000;
            res += dp[i / 1_0000] + dp[next] + (next == 0 ? 0 : next > 9 && next < 20 ? 3 : next < 1000 ? 2 : 1);
        }

        System.out.println("从1读到1_0000_0000共读了" + (res + 2) + "个汉字, 耗时" + (System.currentTimeMillis() - start) + "ms");
    }

}

// 结果
从1读到1_0000_0000共读了1403898993个汉字, 耗时201ms

总结

这题真是比我想象中更复杂,总算是根据大家在某乎上的答案写出了动态规划的版本。使用动态规划确实能够让时间复杂度变得更低,但是对于比较复杂的问题,需要更严谨的分析才能拿到正确的转移方程。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,509评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,806评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,875评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,441评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,488评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,365评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,190评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,062评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,500评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,706评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,834评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,559评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,167评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,779评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,912评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,958评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,779评论 2 354