剑指Offer 43. 1~n整数中1出现的次数

剑指Offer 43. 1~n整数中1出现的次数

输入一个整数 n ,求1~n这n个整数的十进制表示中1出现的次数。
例如,输入12,1~12这些整数中包含1 的数字有1、10、11和12,1一共出现了5次。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/1nzheng-shu-zhong-1chu-xian-de-ci-shu-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

转载自我的LeetCode题解

这是一道典型的数位动态规划题,掌握了数位动态规划的思想和模板后可以秒杀。
首先,使用暴力枚举、拆分所有数字的解法超时。于是,可以从改进枚举的角度来更新算法。
以12345为例,要枚举这么多数,可以这样做:从最高位开始,当最高位为0时(即最高位没有达到它的上界1),后面的一位数字可以取从0到9的数字;当最高位为1时(即最高位达到了它的上界1),后面的一位数字只能取从0到2的数字(因为2是这位数字的上界)。依此类推,可以枚举完所有的数字。这样,我们就理解了数位动态规划的第一步,暴力枚举数字。很明显,在枚举的过程中,我们要标记前面的数位是否到达了上界,来决定当前数位的枚举范围。
这样做有一个很核心的好处,即本质上是按位枚举,省去了原来的拆分数字的过程,即只要某一位枚举到了1,我们就对总数+1。
再进一步,单纯的递归写法会超时,因为有许多计算是重复的,所以在递归的过程中,记录每次的结果,需要用的时候直接拿来使用。空间换时间。于是,需要数组来寄存中间结果。这里要明白数组是来寄存对应的一次递归的结果,而递归函数是有参数的,因此把参数作为数组的下标来标记这一次递归的结果。
详细代码如下:

class Solution {
    
    
    private int[][][] dp = null;
    private int[] upperBound = null;

    public int countDigitOne(int n) {
    
    
        String string = String.valueOf(n);
        char[] chars = string.toCharArray();//实现对n的从最高位到最低位数位拆分
        int length = chars.length;
        upperBound = new int[length];//upperBound数组记录从n的最低位到最高位,每一位数字的上限
        for(int i = 0; i < length; ++i) {
    
    
            upperBound[i] = chars[length - 1 - i] - '0';//初始化upperBound数组,注意下标的变换,因为记录的是从n的最低位到最高位的数字上限
        }       
        dp = new int[length][2][length];//int[pos][limit][sum],下标对应dfs的参数,标识、记录中间结果
        for(int i = 0; i < length; ++i) {
    
    
            for(int j = 0; j < 2; ++j) {
    
    
                Arrays.fill(dp[i][j], -1);//初始化dp数组,用-1表示对应的dfs有没有执行过
            }
        }
        return this.dfs(length - 1, 1, 0);
    }

    private int dfs(int pos, int limit, int sum) {
    
    //pos与upperBound的下标对应
        if(pos == -1) return sum;//递归结束,返回sum
        if(dp[pos][limit][sum] != -1) return dp[pos][limit][sum];//对应的dfs执行过了,就直接返回以前算过的值,空间换时间,节省时间开销
        int maxNum = (limit == 1) ? upperBound[pos] : 9;//当前第pos位的数有上界要求,取上界数字,否则取9
        int res = 0;
        for(int i = 0; i <= maxNum; ++i) {
    
    //对当前第pos位的数字进行枚举,范围从0到maxNum
            //考虑低一位的数字:当当前第pos位有上界且达到了上界,低一位数字才有上界;
            //当当前第pos位为1时,1出现的次数加1,因此sum需要加1
            res += dfs(pos - 1, ((limit == 1) && (i == maxNum)) ? 1 : 0, (i == 1) ? sum + 1 : sum);
        }
        return dp[pos][limit][sum] = res;//标识、记录res,并返回res
    }
}

猜你喜欢

转载自blog.csdn.net/m0_55570281/article/details/114156236