关于f(n)=n的几种算法

来源:互联网 发布:仁以为己任 不亦重乎 编辑:程序博客网 时间:2024/06/06 12:33

题目:
有一个整数n,写一个函数f(n),返回0到n之间出现的"1"的个数。比如f(13)=6,现在f(1)=1,问下一个最大的f(n)=n的n是什么?

/*p228面试题5
Consider a function which, for a given whole number n, returns the number of ones required when writing out all numbers between 0 and n.  

For example, f(13)=6. Notice that f(1)=1. What is the next largest n such that f(n)=n? 

e.g. 
f(13)=6,  
because the number of "1" in 1,2,3,4,5,6,7,8,9,10,11,12,13 is 6. (1, 11, 12, 13) 

tks  
现在要我们写一个函数,计算4,000,000,000以内的 最大的那个f(n)=n的值,要求程序能在100ms里面运行完毕

 

算法1。大家自然想到一个最简单的算法如下:

#include "stdafx.h"#include "stdio.h"#include <windows.h>//统计一个数字中1的个数int NumberofOne(int num){int result = 0;while(num>0){int r = num;if(r == 1) result++;num /= 10;}return result;}int main(){int sum = 0;for(int i=0;i<4000000000;i++){sum += NumberofOne(i);if(sum == i){printf("f(%d) = %d\n",i,sum);}}return 0;}


算法二(这个偶看得晕晕乎乎的):基于以下考虑:

1、两个函数i和f(i)都是单增函数,故有:

1、计算n位最大数的f值,例如f(9) = 1;

所有小于和等于两位的数含1的总和f(99) = 100*2/10;

类似的,f(999) = 1000*3/10=300
...

这样子的话,比如f(40001),从0-9999很显然有4000个1,从10000-19999有4000+10000个1,从20000-29999还是4000个1,...
所以f(40001)=4*4000+10000+1
称以上优化为后段优化

2、计算i和f(i)之间的差值。
假设:diff = i - f(i);
(1)如果diff>0,说明f(i) < i。
考虑f(i)的增长速度:显然此时f(i)的增加速度<=i的位数。例如i是二位数,f(20) = 11; 差值diff = 20-11 = 9;要让下一个f(i)有可能追上i,想一下,i每增加一,即多一个数字,这个数字含1的个数<=它的位数,现在要让下一个i满足f(i)增加11,这里i是两位数,所以一个数字至多多两个1,所以要让至少i要增加9/2=4
至少i要增加9/2=4次,f(i)才有可能追上i的增长。因此可以直接计算f(20+4);


(2)如果diff<0, 说明f(i) > i。例如f(1622581) = 1642401;差值diff = 1622581 - 1642401 = -19820;假如下一个i,使得f(i)增加了x,那么i要增加19820+x才能达到f(i)=i,由于x>=0,所以i至少要增加19820,所以可以直接考虑计算f(1622581 + 19820),也就是计算f(1642401)即可。
称以上优化为前段优化

综合以上两点,写出如下算法:

#include "stdafx.h"#include "stdio.h"#include <windows.h>//计算从1到num的1的个数UINT NumberOneBelow(UINT num){UINT mod = 1;UINT add = 0;UINT result = 0;while(num>0){UINT chu = num/10;UINT yu = num%10;result += chu * mod;if(yu > 1) result += mod;if(yu ==1) result += (add+1);add += yu * mod;mod *= 10;num /= 10;}return result;}//计算n位数之内1的总数统计值,这里的num只考虑它的位数UINT Total(UINT num){UINT key = num;UINT add = 1;while(key>9){add*=10;key/=10;}if(num<10)return 1;else return 10*Total(num/10)+add;}//计算num相关的最大值和位数。例如num是二位数,则max = 99, bit = 2void MaxAndBit(UINT num, UINT &max, UINT &bit){max = 9;bit = 1;while(num>9){num/=10;max*=10; max+=9;bit++;}return ;}int main(){UINT max, bit;UINT TotalOnes = Total(1);MaxAndBit(1,max,bit);int beginTime=GetTickCount();for(UINT i=1;i<4000000000;i++){//如果i超过n位之内所有1的个数,则跳过后面的计算if(i>TotalOnes){i = max;TotalOnes = Total(i+1);MaxAndBit(i+1,max,bit);//超过最大整数表示范围,用固定数替换if(TotalOnes>1000000000){TotalOnes = 4000000000; //实际上,应该是100亿}continue;}else{UINT one = NumberOneBelow(i);if(one == i){printf("f(%d)=%d\n",i,one);}else{//计算差值int diff = i-one;if(diff > 0){i+= diff/bit;}else{i = one-1;//下个循环i自动加1后,自动从one开始计算}}}}int endTime=GetTickCount();endTime-=beginTime;printf("time: %d ms\n",endTime);return 0;}



计算到最后一个结果f(1111111110) = 1111111110; 所用时间在16 - 47毫秒之间。

 注:算法三只是一个求0~N的1的个数的快速算法,不是上面的题目的要求。

算法三:

如果N是一位数,可以确定f(N)=1

如过是二位数,如果 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 位数、 位数。   根据上面的一些尝试,下面我们推导出一般情况下,从 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,一共114 个,等于低位数字(123)+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)。通过上面的归纳和总结,我们可以写出如下的更高效算法来
计算 f(N):

#include<stdio.h>int Sumls(int n){    int iCount=0,iFactor=1,iLowerNum=0,iCurrNum=0,iHigherNum=0;    while(n/iFactor!=0)    {        iLowerNum=n-(n/iFactor)*iFactor;        iCurrNum=(n/iFactor)%10;        iHigherNum=n/(iFactor*10);               switch(iCurrNum)        {            case 0:                iCount+=iHigherNum*iFactor;                break;            case 1:                iCount+=iHigherNum*iFactor+iLowerNum+1;                break;            default:                iCount+=(iHigherNum+1)*iFactor;                break;                               }        iFactor*=10;           }    return iCount; }int main(){    int x;    scanf("%d",&x);       printf("%d",Sumls(x));    return 0;}


程序员面试宝典上的解答,没看懂cal函数
#include<iostream>using namespace std;#include<stdlib.h>#include<windows.h>int gtable[10];const unsigned int gmax = 4000000000l;int f(int n){int ret =0;int ntemp =n;int ntemp2 =1;int i=1;while(ntemp){ret+=(((ntemp-1)/10)+1)*i;if((ntemp%10)==1){ret -=i;ret +=ntemp2;}ntemp = ntemp/10;i *=10;ntemp2 = n%i+1;}return ret;}int count1(int n){int count =0 ;while(n){if((n%10)==1)++count;n /=10;}return count;}int cal(unsigned int number,int nwei,int count1,unsigned int ncount){int i,n=1;unsigned int maxcount;if(nwei==0){ncount +=count1;if(number == ncount){cout<<"f("<<number<<")="<<number<<endl;}return ncount;}for(i=0;i<nwei;i++)n*=10;maxcount = ncount + gtable[nwei-1];maxcount +=count1*n;if(ncount>(number +(n-1))){return maxcount;}if(maxcount<number){return maxcount;}n /=10;for(int i=0;i<10;++i){if(i==1)ncount = cal(number +i*n,nwei-1,count1+1,ncount);elsencount = cal(number+i*n,nwei-1,count1,ncount);}return ncount;}void main(){int i;unsigned int n=1;unsigned int ncount=0;int nwei=0;int ncount1;int begintime =gettickcount();int endtime;for(i=0;i<10;++i){n *=10;gtable[i]=f(n-1);cout<<gtable[i]<<endl;}n =0;nwei = 0;ncount1=0;while(n<gmax){unsigned int temp;temp =1;ncount = cal(n,nwei,ncount1,ncount);for(i=0;i<nwei;++i)temp *=10;n +=temp;if((n/temp)/10==1)++nwei;ncount1 = count1(n);}endtime =gettickcount();endtime -=begintime;cout<<"time:"<<endtime<<"ms\n";}


原创粉丝点击