剑指offer(43):详解1 ~n 整数中 1 出现的次数

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/dugudaibo/article/details/79658842

本博客主要内容为图书《剑指offer》43 题的解题思路及代码。方法可能还有不足之处,欢迎大家讨论评论。

1. 题目描述

  输入一个整数 n ,求出 1~n 这 n 个整数的十进制表示中 1 出现的次数。例如输入 12,1 ~ 12 这些整数中包含 1 的数字有 1 ,10,11,12,“1 ”一种出现了 5 次。(注意其中 11 中 1 出现了两次)

2. 解题思路

2.1 最高位为 1 的情况

  对于某一个数字,如 1234 ,考虑 1~1234 中 1 一共出现了多少次的时候,可以进行如下分解:

1 : 1234 = 1 : 999 + 1000 : 1234

这个时候只需要分 1 : 999 和 1000 : 1234 两步进行考虑就行。

  对于第一部分,我们可以观察到一个比较强的规律。当数字是一位数的时候,数字的长度 l e n g t h = 1 ,也就是说 1~9 只包含 1 个 1,即

f ( l e n g t h = 1 ) = 1

当数字是二位数的时候,数字的长度 l e n g t h = 2 ,我们可以将 1~99在分解为三个部分,即
1 : 99 = 1 : 9 + 10 : 19 + 20 99

其中 1~9 的结果为 f ( l e n g t h ) = 1 , 10~19 一共有十个数(二十个数字),其中十位都是1 ,有 10 个1;剩下 10个个位的情况是 0~9 中有多少个 1 ,这个情况与 1~9 的结果是相同的;20~99有 ( 9 2 + 1 ) 个1,即
f ( l e n g t h = 2 ) = 1 + ( 10 + 1 ) + ( 9 2 + 1 )

通过进行归纳可以得到下面的通式
f ( l e n g t h ) = 10 n 1 + 10 f ( l e n g t h 1 )

其中的 length 是对应数字的长度。

  对于第二部分,即 1000 : 1234 ,其中最高位重复了 1234 1000 + 1 次,即 n 10 l e n g t h 1 + 1 ,然后考虑除了最高位剩下位(0~234)中 1 出现的次数,即计算 g ( n 10 l e n g t h 1 )

  所以数字最高位为 1 的情况的计算表达式如下:

g ( n ) = f ( l e n g t h 1 ) + n 10 l e n g t h 1 + 1 + g ( n 10 l e n g t h 1 )

其中的 f ( l e n g t h ) = 10 n 1 + 10 f ( l e n g t h 1 ) ,n 是输入的数字,length 是输入数字的长度。

2.2 最高位>1 的情况

  对于某一个数字,如 4234 ,考虑 1~4234 中 1 一共出现了多少次的时候,可以分解如下的四个部分:

1 : 4234 = 1 : 999 + 1000 : 1999 + 2000 : 3999 + 4000 : 4234

其中 1:999 可以通过 f 计算得到;1000:1999 中最高位出现的次数为 10 l e n g t h 1 ,而剩下的位数出现 1 的总次数为 f ( l e n g t h 1 ) ;2000:3999中最高位不会出现 1 ,剩下的位出现 1 的问题又变成了 1:999,即 ( 3 2 + 1 ) f ( l e n g t h 1 ) ,最后剩下 4000:4234 ,而这个问题等价为 0:234 中有多少个 1 的问题,可以通过 g ( n a 10 l e n g t h 1 ) 来解决。所以此时的通式为
g ( n ) = 10 l e n g t h 1 + a f ( l e n g t h 1 ) + g ( n a 10 l e n g t h 1 )

其中的 a 是最高位的数字

扫描二维码关注公众号,回复: 2895051 查看本文章

2.3 情况总结

  所以计算这个问题的递推公式如下:

g ( n ) = { f ( l e n g t h 1 ) + n 10 l e n g t h 1 + 1 + g ( n 10 l e n g t h 1 ) , if 最高位为1 10 l e n g t h 1 + a f ( l e n g t h 1 ) + g ( n a 10 l e n g t h 1 ) , if 最高位非1

其中
f ( l e n g t h ) = 10 n 1 + 10 f ( l e n g t h 1 )

所以总体的思想就是,根据输入数字的最高位对情况进行分类,之后带入上面的递推公式,进行递归操作。递归的停止条件为剩下的数字的长度为 1,如果剩下的数字是0返回0,否则返回1.

3. 代码实现

# -*- coding:utf-8 -*-
class Solution:
    def NumberOf1Between1AndN_Solution(self, n):
        # 如果数字为 0 则返回 0
        if not n:
            return 0

        # 将数字转化为字符串,得到数字的长度
        strN = str(n)
        length = len(strN)

        # 通过 f 函数计算递推通式中的 f (length -1)
        mydicdictionary = self.dictionary(length-1)

        #如果长度为 1 且不为 0 话就返回 1 
        if length == 1:
            return 1

        # 求出当前数字的最高位数字
        temp = 10**(length-1)
        a = n//temp

        # 根据判断结果执行递推公式
        if n-temp >= temp:
            return temp + a*mydicdictionary + self.NumberOf1Between1AndN_Solution( n-a*temp)
        else:
            return mydicdictionary + n-temp + 1 +self.NumberOf1Between1AndN_Solution( n-temp)

    def dictionary(self,length):
        mydicdictionary = [0]
        for i in range(1 , length+1):
            result = 10**(i-1) + 10*mydicdictionary[i-1]
            mydicdictionary.append(result)
        return mydicdictionary.pop()

猜你喜欢

转载自blog.csdn.net/dugudaibo/article/details/79658842