LeetCode 233. 数字1的个数

题目链接 https://leetcode-cn.com/problems/number-of-digit-one/description/

目标:

问题转化:求最高位对1的贡献

例如输入193,我们先求1作为最高位百位对1的贡献,假设结果为c1;然后我们将最高位1去掉,就剩下93,我们求得9作为最高位十位对1的贡献为c2;同样我们去掉9,剩下3,我们求得3作为最高位个位对1的贡献是c3。

答案 = c1+c2+c3。

那么,求最高位对1的贡献怎么求?按照以下规则:

如果n是一位数,最高位就是个位,由于没有比它更小的位数了,因此对1的贡献只有它本身(个位),如果各位数字大于等于1,贡献是1,否则贡献是0。

如果n是两位数,最高位就是十位,因此对1的贡献分为0-9部分和十位本身。0-9:贡献能力是1,再乘以十位数字就是贡献1的个数;十位:贡献大小取决于十位数字,如果十位数字大于1,则贡献是10;如果十位数字等于1,则贡献是去掉十位剩下的数+1。

如果n是三位数,最高位就是百位,因此对1的贡献分为0-99部分和它本身(百位)。0-99:贡献能力是20,百位:贡献大小取决于百位数字,如果百位数字大于1,则贡献是100;如果十位数字等于1,则贡献是去掉百位剩下的数+1。

如果n是四位数,最高位就是千位,因此对1的贡献分为0-999部分和它本身(千位)。0-999:贡献能力是300,千位:贡献大小取决于千位数字,如果千位数字大于1,则贡献是1000;如果千位数字等于1,则贡献是去掉千位剩下的数+1。

以此类推。。。。。。

举例说明:

413是个三位数,我们这里先考虑最高位4对1的贡献,根据上面的规则,贡献分为0-99部分和百位部分。首先说0-99贡献:0-99共有20个1,因此贡献能力是20,一共有四次(0-99、100-199、200-299、300-399),因此共贡献了4*20 = 80个1;再说百位的贡献:因为百位数字是4,大于1,因此百位的贡献就是100(100、101、...、199一共100个 1)。因此,413最高位4对1的贡献 = 4*20+100 = 180。

你可能会问,这里只考虑最高位4对1的贡献,后面的13也贡献了6个1。没错,因此我们考虑完4对1的贡献后,就要将4剔除,剩下13,我们还是套用此规则。

13是个两位数,我们这里只找最高位1对1的贡献,根据上面的规则,贡献分为0-9部分和十位部分。首先说0-9贡献:0-9共有1个1,因此贡献能力是1,一共有1次(0-9),因此共贡献了1*1 = 1个1;再说十位的贡献:因为十位数字等于1,因此十位的贡献就是4(去掉十位后的数+1,即3+1,因为10、11、12、13)。因此,13最高位十位1对1的贡献 = 1*1+4 = 5。

然后剔除十位1,只剩下3了,3大于等于1,所以贡献是1。

因此结果 = 4*20+100 + 1*1+4 + 1 = 186。

0-9贡献能力是1,0-99贡献能力是20,0-999贡献能力是300,0-9999贡献能力是4000,这是怎么来的?

1*10+10 = 20;20*10+10^2=300;300*10+10^3=400。就是这个规律。

最后上代码:

class Solution {
	int GetContribution1(int nDigit){
		if (nDigit == 1)
		{
			return 0;
		}

		int nTem = nDigit - 2;
		int nRes = 1;
		int i = 1;
		while(nTem-- > 0)
		{
			nRes = nRes * 10 + power10(i++);
		}
		return nRes;
	}

	// 10的n次幂
	int power10(int n)
	{
		int nTem = n;
		int res = 1;
		while (nTem > 0)
		{
			res *= 10;
			nTem--;
		}
		return res;
	}

public:
	int countDigitOne(int n) {
		int nRes = 0;

		// 定义一个数组记录输入的每一位
		vector<int> vecNum;
		int nTem = n;
		while(nTem)
		{
			vecNum.push_back(nTem % 10);
			nTem /= 10;
		}

		nTem = n;
		while(vecNum.size() > 0)
		{
			// 当前共有几位
			int nCurWei = vecNum.size();
			// 当前最高位是多少
			int nHigh = vecNum.back();
			// 当前最高位如果是0,则对1没有贡献
			if (nHigh > 0)
			{
				// 贡献的第一部分
				nRes += GetContribution1(nCurWei) * nHigh;

				// 贡献的第二部分
				if (nHigh == 1)
				{
					nRes += nTem % power10(nCurWei - 1) + 1;
				}
				else
				{
					nRes += power10(nCurWei - 1);
				}
				// nTem表示去除最高位剩下的数
				nTem %= power10(nCurWei - 1);
			}
			vecNum.pop_back();
		}

		return nRes;
	}
};

时间复杂度应该是O(log N)。

水平有限,欢迎批评。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值