求0到数字n中的所有数字中某一个数字k出现的总次数

今天在leetcode上面遇到了一道题:

编写一个方法,计算从 0 到 n (含 n) 中数字 2 出现的次数。

这道题最好的解法是动态规划。分析如下:设dp[i]标志这个数字前i位中出现了2的次数,那么我们需要求的是dp[i]和的dp[i - 1]的关系。比如说一个数字2849,那么dp[2]就表示0~49中出现2的数量,dp[3]就表示0~849中出现2的数量,dp[4]就表示0~2849中出现2的数量。分析如下:

因为我们要求的是2出现的次数,那么根据第i位的数字,我们可以分类讨论:

1. 第i位是0
        例如:n = 102, 分析dp[2]和dp[1]的关系,即dp(02)与dp(2) (02就是2,两者的结果是相同的,但是0又是不可或缺的)
第i位是0,该位取值范围只有这一种可能,2的数量也不可能增加,由此可得
        dp[02] = dp[2]


2. 第i位是1
        例如:n = 178,分析dp[3]和dp[2]的关系,即dp(178)与dp(78)(括号就表示range(0~178),实际上的dp[3]和dp[2]的关系)第3位是1,该位可能取0,1两种情况:
dp[3] = 当第3位是0,1-2位取00~99时2的次数 + 当第3位是1, 1-2位取00~78时2的次数
        dp[3] = dp(99) + dp[2]
        dp(178) = dp(99) + dp(78)

3. 第i位是2
        例如:n = 233, 分析dp[3]和dp[2]的关系,即dp(233)与dp(33)
        dp[3] = 第3位取0-1,1-2位取00~99时2的次数 + 第3位是2,1-2位取00~33时2在1-2位出现的次数 + 第3位是2,1-2位取00~33时2在第3位出现的次数.
        dp[3] = 2 * dp(99) + dp[2] + 34(实际上是33 + 1)
        dp(233) = 2 * dp(99) + dp(33) + 34

4 第i位大于2
        以 n = 478为例,分析dp[3]和dp[2]的关系,即dp(478)与dp(78)
        dp[3] = 第3位取0-3,1-2位取00-99时2出现在1-2位的次数 + 第3位取4,1-2位取00-78时2的次数 + 第3位取2,1-2位取00-99时2出现在第3位的次数
        dp[3] = 4 * dp(99) + dp[2] + 100

        总结来看,一个数字n,假设第i位为k, 那么dp[i] = k * dp(99) + dp[i - 1] + n % pow(10, i - 1) + 1(如果k==2) + pow(10, i -1)(如果k > 2)

        所以我们存两种东西:一种是真正的dp[i][0]表示前i位数字包含2的数量,第二种是dp[i][1]表示99...(一共i个9)...99包含的2的数量,也就是dp(99...(一共i个9)...99)

代码如下:

class Solution {
public:
	int numberOf2sInRange(int n) {
		int digit = getdigit(n);
		vector<vector<int>> dp(digit + 1, vector<int>(2, 0));//dp[i][0]表示从一共i位数字情况下出现2的个数,di[i][1]表示99...9一共i个9的情况2的数量
		dp[1][0] = (n % 10 <= 1) ? 0 : 1;
		dp[1][1] = 1;
		for (int i = 2; i <= digit; i++){
			int k = getk(n, i);
			int cnt = k * dp[i - 1][1] + dp[i - 1][0];
			if (k == 2){
				cnt += (n % (int)pow(10, i - 1)) + 1;
			}
			else if (k > 2){
				cnt += (int)pow(10, i - 1);
			}
			dp[i][0] = cnt;
			//try{
				dp[i][1] = 10 * dp[i - 1][1] + (int)pow(10, i - 1);
			//}
			//catch(exception& e){

			//}
				
		}
		return dp[digit][0];
	}

	//获得n有多少位数
	int getdigit(int n){
		int cnt = 0;
		while (n > 0){
			n /= 10;
			cnt++;
		}
		return cnt;
	}
	//获得数字n 从右边第k位置的数字是多少
	int getk(int n, int k){
		for (int i = 1; i < k; i++) n /= 10;
		return n % 10;
	}
};

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值