题目:
给定一个十进制的正整数,写下从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”。
最近老师给的题目都来自《编程之美》里面,题目很典型,主要都是讲究思路和算法,有机会要看看这本书。