剑指offer:面试题43 1~n整数中1出现的次数(数位DP)

面试题43. 1~n整数中1出现的次数

这道题我很喜欢,有用的知识增加了。
首先明确这道题可以用数位DP来做:
先上一个数位DP模板:

#include <bits/stdc++.h>
using namespace std;
//问:在区间[a,b]中不含49的数字有多少个
int a, b, shu[20], dp[20][2];              
//shu数组存储各位上的数  dp[i][1] 表示第i+1位为4,剩余数字长度为i的数字的个数
//dp[i][0] 意思第i+1位为除4外任意一个数字时,剩余数字长度为i的数字的个数。不是所有不为4的数剩余数字长度为i的数字的个数之和  如dp[1][0]=9,dp[1][1]=8
int dfs(int len,bool if4,bool limit)   //len 遍历的每一位(...千,百,十,个),len=1表示个位.len=2表示十位...  if4 此位的前一位是否为4, shangxian, 此位数是否有上限, 例如前两位是1, 2, 第三位个位有上限7
{		
	//然后开始在len位上遍历数字
    if (len==0)
        return 1;
    if (!limit&&dp[len][if4]!=0)   //记忆化搜索这里可以直接返回结果,
        return dp[len][if4];       //如对数字127,当前两位为1,0和 1,1 的时候由于最后一位没有limit,故结果相同, 但与1,2情况不同,因为为1,2时第三位有limit了
    int cnt=0,maxx;
	if(limit)
		maxx=shu[len];		//有上限的话最大取shu[len]的值
	else
		maxx=9;			//没上限的话最大取9
    for(int i=0;i<=maxx;i++)		//上限记录到了maxx中,从0开始依次取
    {
        if(if4&&i==9)
            continue;		//如果上一位值为4且当前位值为9,直接进入下一循环
        cnt+=dfs(len-1,i==4,limit&&i==maxx);  //只有 当前有限制 且 现在已经达到了上限(maxx)才能构成下一位有限制
    }	//由于在这个循环中len没有变,只是前一位数字变了,所以在进入下一个dfs函数,如果没有限制则返回值与之前得到的就相同,dp[len][if4]。
    if(!limit)
    	dp[len][if4]=cnt;	//如果有限制,那么就不能记忆化,否则记忆的是个错误的数.例如前两位12,由于第三位有上限7不能保存这个结果,而10和11结果相同,无上限, 记忆化保留结果
    return cnt;
}

int solve(int x)
{
    memset(shu,0,sizeof(shu));
    int k=0;
    while(x){
        shu[++k]=x%10;  //保存a,b的数
        x/=10;
    }
    return dfs(k,false,true);//从最高位开始
}
int main(){
    scanf("%d%d",&a,&b);
    printf("%d\n",solve(b)-solve(a-1));
    return 0;
}

好了,给出这道题的正解:

1、len的作用

len 遍历的每一位(…千百十个),遍历从高位到低位,len=1表示个位,len=2表示十位。

2、state的作用

state 表示之前所谓位的状态:前面总共出现了多少个1。
例如要求 20 20 20 以内出现了多少个 1 1 1,那么当 l e n = 2 len = 2 len=2 的时候, c n t cnt cnt 分别加上两位数, 0 x 、 1 x 、 2 x 0x、1x、2x 0x1x2x有多少个1,(当十位是 0 0 0 或者 1 1 1 的时候, x x x 不受限制,limit=false,当十位是 2 2 2 的时候, x x x 只能取 0 0 0), l e n = 2 len=2 len=2(十位)的时候, s t a t e = 0 state = 0 state=0,因为十位以前没有 1 1 1
但是当 l e n = 1 len = 1 len=1(个位)的时候, 0 x 0x 0x s t a t e = 0 state = 0 state=0,如果 x x x 1 1 1,就返回 1 1 1,至此 c n t cnt cnt 累计加 1 1 1
l e n = 1 len = 1 len=1(个位)的时候, 1 x 1x 1x s t a t e = 1 state = 1 state=1,如果 x x x 1 1 1,就返回 s t a t e + 1 state+1 state+1,至此 c n t cnt cnt 累计加 s t a t e ∗ 10 + 1 state*10+1 state10+1(除了 11 11 11 返回 2 2 2 之外,其他都是返回 1 1 1)。

3、limit的作用

我们知道在搜索的时候,数位搜索范围可能发生变化;
举个例子:我们在搜索 [0,555] 的数时,显然最高位搜索范围是 0 ~ 5 ,而后面的位数的取值范围会根据上一位发生变化:
1、当百位不是5的时候,十位可以取0~9
2、当百位是5的时候,十位只能取0~5

4、关于DP数组

dp[i][j] 表示第 i 位,之前的高位状态是 j 的时候,有多少种解法。
比如说 dp[1][1]=11,因为 i=1 表示个位,j=1 表示前面只有一个1,那十位肯定就是1,然后10~19共有11个1。
dp 数组起到一个记忆化的作用。
如果前一个高位有限制,那么就不能记忆化,否则记忆的是个错误的数,例如要求12里面有多少个1,前一位是1,已经到达了十位限制的最大数,那么后面只能取0,1,2,最后算出dp[1][1]=4,但是这个4没有考虑13~19,是错误的不可以被重新使用的。

#include <bits/stdc++.h>
using namespace std;
//问:求1~n这n个整数的十进制表示中1出现的次数
int n, shu[32], dp[32][32];
int dfs(int len, int state, bool limit) {
    if (len==0)
        return state;
    if (!limit && dp[len][state]!=0)
        return dp[len][state];
    int cnt=0, maxx;
	if(limit)
		maxx=shu[len];	//有上限的话,上限就是shu[len]的值
	else
		maxx=9;			//没上限的话最大取9
    //上限记录到了maxx中,从0开始依次取
    for(int i=0;i<=maxx;i++) {
        int nstate = state;
        if(i==1) nstate++;
        cnt+=dfs(len-1, nstate, limit&&i==maxx);  
        //只有 “当前有限制” 且 现在已经达到了上限(maxx)才能构成下一位有限制
    }	//由于在这个循环中len没有变,只是前一位数字变了,所以在进入下一个dfs函数,如果没有限制则返回值与之前得到的就相同,dp[len][if4]。
    if(!limit)
    	dp[len][state]=cnt;	
    /**
        如果有限制,那么就不能记忆化,否则记忆的是个错误的数.例如前两位12,
        由于第三位有上限7不能保存这个结果,而10和11结果相同,无上限,记忆化保留结果
    */  
    return cnt;
}
int solve(int x) {
    memset(shu, 0, sizeof(shu));
    int k=0;
    while(x) {
        shu[++k]=x%10;  
        x/=10;
    }
    return dfs(k, 0, true);//从最高位开始
}
int main() {
    scanf("%d",&n);
    printf("%d\n",solve(n));
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值