题目
计算4 000 000 000以内的最大的那个f(n)==n的值,函数f的功能是统计0到n之间所有数字中1的个数和。
思路
这道题需要解决2个问题,求数字1的个数和以及求最大的f(n)=n。
一、子问题1:求数字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 的次数,它将会受到三个因素的影响:百位上的数字,百位以下(低位)的数字,百位(更高位)以上的数字。
(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)。
(2)如果百位上的数字为 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。
(3)如果百位上数字大于 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)。
二、子问题2:求最大的f(n)==n
如果对0~n的所有数据进行枚举验证f(i)=i,效率会非常低,特别是n非常大的时候,比如本题的4000000000。
所以应该通过恰当的方法快速去除那些不可能的数据,剪枝思想就是寻找过滤条件,提前减少不必要的搜索路径。
以下是过滤条件的由来:
(1)当f(n)>n的时候,令c=f(n)-n>0,设b属于[0,c),即0 <=b < c。因为f(n)是一个非递减函数,当n2>n1时,必有f(n2)>=f(n1)。那么有f(n+b)>=f(n)。又因为b < c且c=f(n)-n,所以b < f(n)-n,得出f(n)>n+b。最后得出f(n+b)>=f(n)>n+b。
也就是说只要b属于[0,c),当n递增b的时候,必定有f(n+b)>n+b。因此这些值都可以被剪枝忽略掉。我们取b的上确界值c来说。则有f(n+c)>=f(n)>=n+c。这时才可能出现f(n+c)=n+c的情况。这里是可能,而不是一定。
所以当f(n)>n的时候,选取递增步长c=f(n)-n,令n=n+c=n+f(n)-n=f(n)。
(2)当f(n) < n的时候,题目给的上限是4 000 000 000,这是一个10位数。可以得出结论,当n增加1的时候,f(n)最多增加10。这是一种极端情况,即新增加的那个数是1 111 111 111,所以多了10个1,那么f(n)最多增加10。目前,选取某个步长b,当n+b时,依然有f(n+b) < n+b,但是依然迅速逼近f(n)==n。
假设现在f(n1) < n1,那么想要达到f(n1)=n1的情况。f(n1)至少得增加n1-f(n1),而此时,在之前推出的结论基础上(当n增加1的时候,f(n)最多增加10),可以得出n1最少增加了(n1-f(n1))/10 +1。令n1增加后的结果记为n2=n1+(n1-f(n1))/10 +1。
因此n2 > n1,所以f(n2)>=f(n1),而f(n2)=f{n1+(n1-f(n1))/10 +1} < f(n1)+n1-f(n1)=n1。所以n2>n1>f(n2)。
因此,当f(n) < n的时候,取步长为(n-f(n))/10 +1,这样的话可以迅速逼近f(N)==N。
代码
//
// Created by huxijie on 17-3-14.
// 求f(n)=n
#include <iostream>
using namespace std;
//求0到n之间所有数字中1的个数和
unsigned long fun(unsigned long n) {
unsigned long iCount = 0; //总数
unsigned long iFactor = 1; //基数,用来取数字n的每一位
unsigned long iLowerNum = 0; //较小位的数字
unsigned long iCurrNum = 0; //当前位的数字
unsigned long 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;
}
unsigned long getMaxFunEqualN(unsigned long upperBound) {
unsigned long n = 1, fn = 0;
unsigned long max = 1;
while (n <= upperBound) {
fn = fun(n);
if (fn == n) {
max = n;
n++;
cout<<max<<endl;
} else if (fn < n) {
n += (n - fn) / 10 + 1;
} else {
n = fn;
}
}
return max;
}
int main() {
unsigned long upperBound = 4000000000L;
cout << getMaxFunEqualN(upperBound) << endl;
return 0;
}
参考文献:《程序员面试宝典》第5版:14.5面试例题5