美文网首页
从一读到一亿需要读多少个汉字?

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

作者: qiHuang112 | 来源:发表于2020-01-10 14:58 被阅读0次

前言

刷某乎突然刷到了这个问题,正好自己最近也在写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

总结

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

相关文章

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

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

  • 一兆是多少个汉字?

    原文链接[https://www.zybang.com/question/cf1e29ebeca1a4efc33d...

  • 百万,千万,一亿

    我解读到的水悦星空间,是年收入从一百万,到一千万,到一亿。可能是三年内完成,爆炸式的三个台阶。也可能是它的三个阶段...

  • 正面管教3.25

    书读到结尾了,感觉这次读的不好,需要重读一遍。

  • 有趣的汉字

    汉字是我们的好朋友,汉字可以让我们读到脍炙人口的诗句,可以让我们看到令人回味的文章,汉字还真有趣呢,人们可以运用汉...

  • 我是如何爱上读书的

    研究生一年多以来的求学经历,关于读书,我有很多话要说。 从一开始的读不懂、害怕读、不想读到慢慢的能够读懂、愿意读、...

  • 在两页纸之间

    ——冰天次升 翻开了一页纸 逐字逐字地读下去 认识了这些汉字 但看不到它们之间的跳跃 读到了下一页纸 ...

  • lcd显示汉字——取模加显示

    1、显示汉字,有专门的的字库,但是汉字很多,每一个汉字都需要专门的编码,需要更大的存储空间存放字库,因此需要外部 ...

  • 汉字

    汉字是我们生活中不可缺少的一部分。瞧!街上的广告牌、学校的宣传语、工厂的标语都有汉字的足迹。汉字可以让我们读到脍炙...

  • 读到悟,需要时间

    2020年,读了一些专业的书,还读了一些可视化的书,还有关于教练的书,磕磕绊绊的听了一些理论性很强的网络教研活动!...

网友评论

      本文标题:从一读到一亿需要读多少个汉字?

      本文链接:https://www.haomeiwen.com/subject/rloyactx.html