编程之美: 第二章 数字之魅 2.4 1的数目

/*
1的数目:
给定一个十进制正整数N,写下从1开始,到N的所有整数然后数一下其中出现的所有1的个数
例如:
N=2,写下1,2这样只出现了1个1
N=12,写下1,2,3,4,5,6,7,8,9,10,11,12,这样1的个数是5

问题是:
1写一个函数f(N),返回1到N之间出现的1的个数,比如f(12) = 5,
2满足条件f(N) = N的最大N是多少?

剑指解法:
需要用递归,拆分成三部分:最高位是1  + 除最高位剩余的位数的1 + 递归的1
                         最高位不是1
解法1:
从1开始遍历到N,将其中的每一个数含有1的个数加起来,

解法2:
给定N,分析小于N的数在每一位上可能出现1的次数之和来得到这个结果,对于一个特定的N,分析其中规律:
先看1位数的情况:
如果N=3,那么从1到3的数字是:1,2,3,只有个位数字上可能出现1,而且只出现1次,进一步可以发现如果N是个位数,且N>=1,那么f(N)都等于1
规律:如果N是个位数,且N>=1,f(N) = 1,如果N=0,f(0) = 0

再看两位数的情况:
如果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恰好被计算了两次。

N=23,十位出现1的次数为10次,从10~19,个位出现1的次数为1,11,21,所以f(23) = 10 + 3 = 13
个位数出现1的次数不仅和个位数字有关,还和10位数字有关;如果N的个位数>=1,则个位出现1的次数为十位数的数字加1
                                                                    = 0,则个位出现1的次数为十位数的数字
                                                               十位数=1,则十位数上出现1的次数为个位数的数字加1
															   十位数>1,则十位数上出现1的次数为10

分析3位数:
N=123
个位出现1的个数为13:1,11,21,...,91,101,111,121
十位出现1的个数为20:10~19,110~119
百位出现1的个数为24:100~23

假设N=abcde,这里a,b,c,d,e分别是十进制数N的各个数位上的数字。如果要计算百位上出现1的次数,它收到3个因素的影响,百位上的数字,百位以下,百位以上

如果百位 = 0,百位上出现1的次数由更高位决定:比如12013,百位出现1的情况可能是100~199,1100~1199,2100~2199,。。。,11100~11199,一共有1200个
由更高位的数字12*当前位数100
如果百位上的数字为1,百位出现1的次数不仅受更高位影响,还受低位影响,比如12113,受更高位影响,百位出现1的情况时100~199,1100~1199,2100~2199,。。。,
11100~11199,gong 1200个,等于更高位数字12*当前位数。
受低位影响,百位出现1的情况时12100~12113,等于低位数字113+1

如果百位上数字大于1(即为2~9),则百位上出现1的次数仅由更高位决定,比如12213,则百位上出现1的可能性为:100~199,1100~1199,2100~2199,...,11100~11199,
12100~12199共1300个。=更高位数字加1*当前位数= (12+1)*100

问题2的解法:
要确定最大的数N,满足f(N) = N,分析
9以下为:1个
99以下为:10*1 + 10*1 = 20
999以下为:(99+1)*1 + (9+1)*10 + (0+1)*100 = 100 + 100 + 100 = 300
9999以下为:(0+1)*1000+10*300 = 4000
999 999 999 以下为:900 000 000个
9 999 999 999 以下为:10 000 000 000个
归纳得:f(10^n-1) = n*10^(n-1)
当n>10^10 - 1,f(n)的值大于n,可以猜想,当n大于某一个数N时,f(n)会始终比n大,也就是说最大满足条件在0~N之间,如果能估计出这个N,只要让n从N往0递减,
第一个满足的的数就是要求的整数

问题转化为如何证明上界N存在,并估计该上界N。
令N=10^11-1=99 999 999 999,让n从N往0递减,依次检查是否有f(n) = n,第一个满足条件的就是我们要找的整数。得出n = 1 111 111 110,是满足f(n) =n的最大整数

输入:
2
12
23
123
输出:
1
5
13
57
*/

/*
关键:
1 		lLow = n - (n/lFactor)*lFactor;//参见123 - (123/10)*10
		lCur = (n / lFactor) % 10;//参见(123/10)%10
		lHigh = n / (lFactor * 10);//参见123 / (10*10)

2 		case 0: lCount += lHigh * lFactor;//如果当前位为0,那么当前位出现1的次数 = 高位*位数
			break;
		case 1: lCount += lHigh * lFactor + lLow + 1;//如果当前位为1,那么当前位出现1的次数 = 高位 * 位数 + 低位 + 1
			break;
		default: lCount += (lHigh + 1) * lFactor;//如果当前位>=2,那么当前为出现1的次数 = (高位+1)*位数
3 当n>10^10 - 1,f(n)的值大于n,可以猜想,当n大于某一个数N时,f(n)会始终比n大,也就是说最大满足条件在0~N之间,如果能估计出这个N,只要让n从N往0递减,
第一个满足的的数就是要求的整数

问题转化为如何证明上界N存在,并估计该上界N。
令N=10^11-1=99 999 999 999,让n从N往0递减,依次检查是否有f(n) = n,第一个满足条件的就是我们要找的整数。得出n = 1 111 111 110,是满足f(n) =n的最大整数
long long lN = 1e11 - 1;
*/

#include <stdio.h>

int count1(int n)
{
	int iRet = 0;
	do
	{
		iRet += ( (n % 10 == 1) ? 1 : 0 );
		n /= 10;
	}while(n);
	return iRet;
}

long long count1_divide(int n)
{
	long long lCount = 0;
	long long lHigh,lLow,lCur;
	lHigh = lLow = lCur = 0;
	long long lFactor = 1;
	while(n / lFactor)
	{
		lLow = n - (n/lFactor)*lFactor;//参见123 - (123/10)*10
		lCur = (n / lFactor) % 10;//参见(123/10)%10
		lHigh = n / (lFactor * 10);//参见123 / (10*10)
		switch(lCur)
		{
		case 0: lCount += lHigh * lFactor;//如果当前位为0,那么当前位出现1的次数 = 高位*位数
			break;
		case 1: lCount += lHigh * lFactor + lLow + 1;//如果当前位为1,那么当前位出现1的次数 = 高位 * 位数 + 低位 + 1
			break;
		default: lCount += (lHigh + 1) * lFactor;//如果当前位>=2,那么当前为出现1的次数 = (高位+1)*位数
		}
		lFactor *= 10;
	}
	return lCount;
}

long long findN()
{
	long long lN = 1e11 - 1;
	//long long lN = 1111111110;
	while(lN >= 0)
	{
		if(lN == count1_divide(lN))
		{
			return lN;
		}
		lN--;
	}
	
}

void process()
{
	int n;
	while(EOF != scanf("%d",&n))
	{
		//int iRet = 0;
		//for(int i = 1 ; i <= n ; i++)
		//{
		//	iRet += count1(i);
		//}
		//printf("%d\n",iRet);
		printf("%lld\n",count1_divide(n));
	}
}

void process2()
{
	printf("%lld\n",findN());
}

int main(int argc,char* argv[])
{
	process2();
	//process();
	getchar();
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值