`
SunnyYoona
  • 浏览: 366658 次
社区版块
存档分类
最新评论

[算法系列之三十二]1的数目

 
阅读更多

题目

Given an integer n, count the total number of digit 1 appearing in all non-negative integers less than or equal to n.

For example:
Given n = 13,
Return 6, because digit 1 occurred in the following numbers: 1, 10, 11, 12, 13.

思路一

这个问题看上去并不是一个困难的问题,因为不需要太多的思考,我想大家都能找到一个最简单的方法来计算f(N),那就是从1开始遍历到N,将其中每一个数中含有“1”的个数加起来,自然就得到了从1到N所有“1”的个数的和。

这个方法很简单,只要学过一点编程知识的人都能想到,实现也很简单,容易理解。但是这个算法的致命问题是效率,它的时间复杂度是O(N)×计算一个整数数字里面“1”的个数的复杂度 = O(N * log2 N)。如果给定的N比较大,则需要很长的运算时间才能得到计算结果。比如在笔者的机器上,如果给定N=100 000 000,则算出f(N)大概需要40秒的时间,计算时间会随着N的增大而线性增长。

看起来要计算从1到N的数字中所有1的和,至少也得遍历1到N之间所有的数字才能得到。那么能不能找到快一点的方法来解决这个问题呢?要提高效率,必须摈弃这种遍历1到N所有数字来计算f(N)的方法,而应采用另外的思路来解决这个问题。

代码一

/*---------------------------------------
*   日期:2015-07-18
*   作者:SJF0115
*   题目: 233.Number of Digit One
*   网址:https://leetcode.com/problems/number-of-digit-one/
*   结果:超时
*   来源:LeetCode
*   博客:
-----------------------------------------*/
#include <iostream>
#include <vector>
using namespace std;

class Solution {
public:
    int countDigitOne(int n) {
        if(n <= 9 && n >= 1){
            return 1;
        }//if
        if(n <= 0){
            return 0;
        }//if
        int result = 1;
        for(int i = 10;i <= n;++i){
            result += DigitOne(i);
        }//for
        return result;
    }
private:
    // 计算num中1的个数
    int DigitOne(int num){
        int result = 0;
        while(num){
            if(num % 10 == 1){
                ++result;
            }//if
            num /= 10;
        }//while
        return result;
    }
};

int main(){
    Solution s;
    int n;
    while(cin>>n){
        cout<<s.countDigitOne(n)<<endl;
    }//while
    return 0;
}

思路二

让我们来分析一下对于一个特定的N,如何得到一个规律来分析在每一位上所有出现1的可能性,并求和得到最后的f(N)。

先从一些简单的情况开始观察,看看能不能总结出什么规律。

先看1位数的情况。

如果N = 3,那么从1到3的所有数字:1、2、3,只有个位数字上可能出现1,而且只出现1次,进一步可以发现如果N是个位数,如果N>=1,那么f(N)都等于1,如果N=0,则f(N)为0。

再看2位数的情况。

如果N=13,那么从1到13的所有数字:1、2、3、4、5、6、7、8、9、10、11、12、13,个位和十位的数字上都可能有1,我们可以将它们分开来考虑,个位出现1的次数有两次:1和11,十位出现1的次数有4次:10、11、12和13,所以f(N)=2+4=6。要注意的是11这个数字在十位和个位都出现了1,但是11恰好在个位为1和十位为1中被计算了两次,所以不用特殊处理,是对的。再考虑N=23的情况,它和N=13有点不同,十位出现1的次数为10次,从10到19,个位出现1的次数为1、11和21,所以f(N)=3+10=13。通过对两位数进行分析,我们发现,个位数出现1的次数不仅和个位数字有关,还和十位数有关:如果N的个位数大于等于1,则个位出现1的次数为十位数的数字加1;如果N的个位数为0,则个位出现1的次数等于十位数的数字。而十位数上出现1的次数不仅和十位数有关,还和个位数有关:如果十位数字等于1,则十位数上出现1的次数为个位数的数字加1;如果十位数大于1,则十位数上出现1的次数为10。

f(13) = 个位出现1的个数 + 十位出现1的个数 = 2 + 4 = 6;
f(23) = 个位出现1的个数 + 十位出现1的个数 = 3 + 10 = 13;
f(33) = 个位出现1的个数 + 十位出现1的个数 = 4 + 10 = 14;
.........
f(93) = 个位出现1的个数 + 十位出现1的个数 = 10 + 10 = 20;

接着分析3位数。

如果N = 123:

个位出现1的个数为13:1, 11, 21, …, 91, 101, 111, 121
十位出现1的个数为20:10~19, 110~119
百位出现1的个数为24:100~123

f(23)= 个位出现1的个数 + 十位出现1的个数 + 百位出现1的次数 = 13 + 20 + 24 = 57;

同理我们可以再分析4位数、5位数。读者朋友们可以写一写,总结一下各种情况有什么不同。

根据上面的一些尝试,下面我们推导出一般情况下,从N得到f(N)的计算方法:

假设N=abcde,这里a、b、c、d、e分别是十进制数N的各个数位上的数字。如果要计算百位上出现1的次数,它将会受到三个因素的影响:百位上的数字,百位以下(低位)的数字,百位(更高位)以上的数字。

如果百位上的数字为0,则可以知道,百位上可能出现1的次数由更高位决定,比如12 013,则可以知道百位出现1的情况可能是100~199,1 100~1 199,2 100~2 199,…,11 100~11 199,一共有1 200个。也就是由更高位数字(12)决定,并且等于更高位数字(12)×当前位数(100)

如果百位上的数字为1,则可以知道,百位上可能出现1的次数不仅受更高位影响,还受低位影响,也就是由更高位和低位共同决定。例如对于12 113,受更高位影响,百位出现1的情况是100~199,1 100~1 199,2 100~2 199,…,11 100~11 199,一共1 200个,和上面第一种情况一样,等于更高位数字(12)×当前位数(100)。但是它还受低位影响,百位出现1的情况是12 100~12 113,一共14个,等于低位数字(13)+1

如果百位上数字大于1(即为2~9),则百位上可能出现1的次数也仅由更高位决定,比如12 213,则百位出现1的可能性为:100~199,1 100~1 199,2 100~2 199,…,11 100~11 199,12 100~12 199,一共有1 300个,并且等于更高位数字+1(12+1)×当前位数(100)

这个方法只要分析N就可以得到f(N),避开了从1到N的遍历,输入长度为Len的数字N的时间复杂度为O(Len),即为O(ln(n)/ln(10)+1)。在笔者的计算机上,计算N=100 000 000,相对于第一种方法的40秒时间,这种算法不到1毫秒就可以返回结果,速度至少提高了40 000倍。

代码二

/*---------------------------------------
*   日期:2015-07-19
*   作者:SJF0115
*   题目: 233.Number of Digit One
*   网址:https://leetcode.com/problems/number-of-digit-one/
*   结果:AC
*   来源:LeetCode
*   博客:
-----------------------------------------*/
#include <iostream>
#include <vector>
using namespace std;

class Solution {
public:
    int countDigitOne(int n) {
        if(n == 0){
            return 0;
        }//if
        int result = 0;
        int lowerNum = 0,curNum = 0,highNum = 0;
        int base = 1;
        int num = n;
        while(num){
            // 低位部分
            lowerNum = n - num * base;
            // 当前部分
            curNum = num % 10;
            // 高位部分
            highNum = num / 10;
            // 如果为0则这一位1出现的次数由更高位决定 (更高位数字*当前位数)
            if(curNum == 0){
                result += highNum * base;
            }//if
            // 如果为1则这一位1出现的次数不仅受更高位影响还受低位影响(更高位数字*当前位数+低位数字+1)
            else if(curNum == 1){
                result += highNum * base + (lowerNum + 1);
            }//else
            // 大于1则仅受更高位影响((更高位数字+1)*当前位数)
            else{
                result += (highNum + 1) * base;
            }//else
            num /= 10;
            base *= 10;
        }//while
        return result;
    }
};

int main(){
    Solution s;
    int n;
    while(cin>>n){
        cout<<s.countDigitOne(n)<<endl;
    }//while
    return 0;
}
<script type="text/javascript"> $(function () { $('pre.prettyprint code').each(function () { var lines = $(this).text().split('\n').length; var $numbering = $('<ul/>').addClass('pre-numbering').hide(); $(this).addClass('has-numbering').parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($('<li/>').text(i)); }; $numbering.fadeIn(1700); }); }); </script>
分享到:
评论

相关推荐

    人工智能(遗传算法)

    在遗传算法中染色体对应的是一系列符号序列,在标准的遗传算法(即基本遗传算法)中,通常用0, 1组成的位串表示,串上各个位置对应基因座,各位置上的取值对应等位基因。遗传算法对染色体进行处理,染色体称为基因个体...

    遗传算法与粒子群算法的实现

    Y表示参与测试的算法数目。 6. Testable是所有可以被TestBatch测试的类需要实现的接口,以提供TestBatch生成结果Excel文件所需要的数据。 7. Domain接口是所有可以被算法解决的问题所需要实现的接口,比如说明该...

    算法分析与设计实验报告

    对于一个正整数n的分划,就是把n表示成一系列正整数之和的表达式。需要注意的是,分划与顺序无关,例如8=3+5和 8=5+3应被认为是同一种划分。另外,这个整数n本身也算是一种划分。 实验二:棋盘覆盖问题 问题分析: ...

    基于Python——Kmeans聚类算法的实现

    本篇博文为数据挖掘算法系列的第一篇。现在对于Kmeans算法进行简单的介绍,Kmeans算法是属于无监督的学习的算法,并且是最基本、最简单的一种基于距离的聚类算法。 下面简单说一下Kmeans算法的步骤: 选随机选取K的...

    论文研究-一种基于旋转最小-最大超盒的聚类算法.pdf

    论文研究-一种基于旋转最小-最大超盒的聚类算法.pdf, ...仿真结果表明,该算法在无需聚类数目的前提下,对复杂分布数据具有很好的聚类效果,其聚类性能与传统的FCA相比有极大的提高。

    论文研究-一种自适应图像去噪混合滤波方法.pdf

    提出一种自适应快速模糊C均值彩色图像分割方法,该方法首先运用蚁群算法,自动获取初始聚类中心和聚类数目,然后使用基于梯度的分水岭算法对原始彩色图像进行预分割,得到一系列由色彩特征空间具有一致性的点构成的...

    MD5加密算法(Java语言描述)

     对MD5算法简要的叙述可以为:MD5以512位分组来处理输入的信息,且每一分组又被划分为16个32位子分组,经过了一系列的处理后,算法的输出由四个32位分组组成,将这四个32位分组级联后将生成一个128位散列值。...

    基于遗传算法的笔式加工刀具路径优化 (2010年)

    笔式加工的特点决定了生成的刀具路径为一系列离散的切削路径段,为了精简搜索数目,将每段刀具路径看作两个遗传基因。使用十进制码构造分段染色体模型,采用改进的遗传算法对笔式加工分段路径进行优化排序。得到刀具...

    ARM NEON技术在车位识别算法中的应用

    摘要:为了在车位检测系统中不使用DSP的情况下,达到实时处理和节约成本的目的,在嵌入式Linux系统中使用了CORTEX-A系列的NEON协处理器技术来优化一种车位图像检测算法的代码。  在CORTEX-A8平台上使用该图像处理...

    嵌入式系统/ARM技术中的ARM NEON技术在车位识别算法中的应用

    摘要:为了在车位检测系统中不使用DSP的情况下,达到实时处理和节约成本的目的,在嵌入式Linux系统中使用了CORTEX-A系列的NEON协处理器技术来优化一种车位图像检测算法的代码。  在CORTEX-A8平台上使用该图像处理...

    计算机系统结构试题集

    1、我们称由( )实现的机器为虚拟机器。 A、硬件 B、软件 C、微程序 D、固件 2、计算机系统结构设计者所关心的是( )所看到的的计算机结构。 硬件设计人员 B、逻辑设计人员 C、机器语言或汇编语言程序员 D、高级...

    《计算机操作系统》期末复习指导

    安全状态、安全系列、银行家算法等 第四章 存储管理 1、存储管理使用的基本概念 •逻辑地址与物理地址 在具有地址变换机构的计算机中,允许程序中编排的地址和信息实际存放在内存中的地址有所不同。前者...

    基于自适应非极大值抑制的SIFT改进算法

    针对图像配准中尺度不变特征变换(SIFT)算法解算速率慢的问题,提出了基于非极大值抑制的改进算法。...仿真试验结果表明,该算法能在一系列不同的图像变换下表现出稳定的配准结果,解算速率较标准SIFT算法提升显著。

    基于FPGA的变频器三电平空间矢量脉宽调制的研究

    采用g-h坐标下的SVPWM算法,以开关状态方程解的数目为依据,对作用时间的计算等问题进行了简化。在理论分析的基础上,对2种算法进行Matlab\Simulink建模。结果表明60°坐标系下SVPWM算法的优越性。本文选用Cyclone II...

    线性超树的孤立点与悬挂边数目* (2006年)

    在先通过引入线性超树的对应二部树的特殊对应性质来刻划超树的顶点与超边的结构,得出了线性超树的孤立点数目的计算公式和一系列推论,从而进一步揭示了度序列与线性超树的关系。然后给出了求线性超树悬挂边数目的...

    2025NOIP普及组.rar

    左两点的路径数目之和,因此我们可以使用逐列(或逐行)递推的方法来求出从起始顶点到重点的路径数目,即使有障碍(我们将马的控制点称为障碍),这一方法也完全适用,只要将到达该点的路径数目置为0即可,用F[i,j]...

    基于微信小程序的智能推荐点餐系统的设计与实现

    在为用户推荐菜品的时候,不能只靠推荐算法产生的结果,还需要结合一定的推荐策略来对物品进行组合排序筛选最后为用户推荐出合适数目且搭配合适的一系列菜品。因此设计出一种推荐策略,根据用餐人数和菜品的分类信息...

    波士顿房价决策树python编码

    本质上决策树是通过一系列规则对数据进行分类的过程。 决策树方法最早产生于上世纪60年代,到70年代末。由J Ross Quinlan提出了ID3算法,此算法的目的在于减少树的深度。但是忽略了叶子数目的研究。C4.5算法在ID3...

    基于一阶循环均值算法的VHF频段信号调制分类识别方法研究

    在估测一阶循环频率系列对应的一阶循环均值的基础上,选择一阶循环均值绝对值超过设定的截止值所对应的一阶循环频率作为候选循环频率,再通过循环平稳测试决定选择候选循环频率数目,实现对信号的调制分类识别,在...

Global site tag (gtag.js) - Google Analytics