关于f(n)=n的几种算法
来源:互联网 发布:apache日志分析工具 编辑:程序博客网 时间:2024/06/06 04:05
题目:
有一个整数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 = 2
void 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):
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;
}
我试着输入100000000瞬间就显示出结果了,编程之美说效率至少提高了40000倍…
- 关于f(n)=n的几种算法
- 关于f(n)=n的几种算法
- 关于f(n)=n的几种算法
- 关于f(n) = f(n-1)*n f(1)=1 求f(100)的值的问题
- 算法设计关于递归方程T(n)=aT(n/b)+f(n)之通用解法
- 算法设计关于递归方程T(n)=aT(n/b)+f(n)之通用解法
- 算法设计关于递归方程T(n)=aT(n/b)+f(n)之通用解法
- f(n)=f(n-1)+f(n-m) 的非递归写法
- 已知f[0]=f[1]=1 f[2]=0 f[n]=f[n-1]+f[n-3] 求 f[0]~f[50]的最大值
- f(n)=f(n-1)+2*f(n-2)+3f(n-3)
- google的一道面世题:(f(n)==n)
- f(n)=n求法程序的重写
- 1-n全排列的几种算法
- %lf\n和%f\n的区别
- 定义Fibonacci数列如下: / 0 n=0 f(n)= 1 n=1 \ f(n-1)+f(n-2) n=2 输入n,用最快的方法求该数列的第n项。
- 关于java的n=n++问题
- 一个递归算法求f(m,n)的问题.
- 谷歌面试题f(n)=n解析
- 内存管理-内存池技术
- MFC--CTreeCtrl
- 字节序(Endian),大端(Big-Endian),小端(Little-Endian)
- 【C++泛型编程】模板偏特化、局部类和型别映射(Int2Type,Type2Type)以及型别选择
- 正则表达式的学习入门实例(java篇)
- 关于f(n)=n的几种算法
- 彻底搞定C指针-函数名与函数指针
- string转char *
- 《Java数据结构和算法》第二版 Robert lafore 编程作业 第七章
- MFC匈牙利命名法
- Java实现树的遍历(前序、中序、后续(递归|非递归)、层次)
- strcpy,strcat和strcmp的实现源代码
- Prüfer编码与Cayley公式
- Visual c++.net 学习目标