题目:
给定一个十进制的正整数,写下从1开始,到N的所有整数,然后数一下其中出现“1”的个数。
要求:
写一个函数 f(N) ,返回1 到 N 之间出现的 “1”的个数。例如 f(12) = 5;
在32位整数范围内,满足条件的“f(N) =N”的最大的N是多少?
设计思想:
(解法一)
开始想到了一个最简单的方法来计算f(N),那就是从1开始遍历,直到N结束,把其中每一个数中含有“1”的个数加起来,结果就是从1到N所有“1”的个数的和。这个方法很简单,但算法的实现效率是个大问题,如果N很大,则需要很长的运算时间才能得到计算结果。
(解法二)
找了一些资料,发现这也是编程之美里的一道题,得到一点启发。首先列出一些数字的情况,来找到其中隐藏的规律:
一位数: f(0)=0、f(1)=1、f(2~9)=1
两位数(以位数是3的为例,[十位]+[个位]):
f(13)=4+2=6、f(23)=10+3=13 …… f(93)=10+10=20
通过分析发现,个位数出现1 的个数和个位数字与十位数有关:如果N的个位数大于等于1 ,则个位出现1的个数为十位数的数字加1;如果N的个位数的数字小于1,则个位出现1的个数为十位数的数字。十位出现1的次数:如果十位数字等于1,则十位出现1的个数为个位数字加1;如果十位数大于1,则十位出现1的个数为10次。
三位数([百位]+[十位]+[个位]):
f(103)=4+10+11=25、f(113)=14+14+12=40、f(123)=24+20+13=57……
f(193)=94+20+20=134、f(203)=100+20+21……
同理分析四位数、五位数……
(1)
源代码:
#include<iostream.h> int f(int n) { int i,unit,decade; int count=0; for(i=1;i<=n;i++) { decade=i; while(decade!=0) { unit=decade%10; decade=decade/10; if(unit==1) { count++; } } } return count; } void main() { int n,count; cout<<"Please input N: "; cin>>n; count=f(n); cout<<"From 1 to "<<n<<",there are "<<count<<" ones."<<endl; }
运行结果:
(2)
源代码:
#include <iostream> #include <cstdio> using namespace std; int f(int n) { int factor=1; int count=0; int high=0; int current=0; int low=0; while(n/factor) { low=n%factor; current=n/factor%10; high=n/factor/10; switch(current) { case 0: count+=high*factor; break; case 1: count+=high*factor+low+1; break; default: count+=(high+1)*factor; break; } factor*=10; } return count; } int main() { int n ; while(scanf("%d",&n)!=EOF) { cout<<f(n)<<endl; } return 0; }
运行结果:
总结:
如果只要求实现基本功能,那实际并不是很难,关键在于当N变得很大时,遍历绝对不是一个好方法,那么就要找到其中隐藏的规律,就像小学奥数一样,在一列数字中,把每个情况都拆开,然后分析,找到隐含的规律。在发现d过程可能要列举很多情况才能发现,也才能肯定最先找到的规律是否正确。不是到确认为止,还要继续扩展,四位数、五位数、六位数……甚至更大,再者就是不确定的N,需要它具有任意性、通用性,而不是单单的特殊的一个数字,类似“abcde”。
最近老师给的题目都来自《编程之美》里面,题目很典型,主要都是讲究思路和算法,有机会要看看这本书。