【题型总结】数位dp(一)

简介: 【题型总结】数位dp(一)

数位动态规划

数位DP介绍

  • 可以解决的问题
    对于「数位 DP」题,都存在「询问[a, b](a 和 b 均为正整数,且 a符合特定条件的数值个数为多少」的一般形式,通常我们需要实现一个查询 [0,x] 有多少合法数值的函数 f(int x),然后应用「容斥原理」求解出 [a,b] 的个数:f(b)−f(a−1)
  • 这个区间可能很大,暴力代码一般会超时,此时就可以使用数位DP解决该类问题。
  • 由于数位是按位dp,数的大小对复杂度的影响很小,时间复杂度为状态个数 * 单个状态的转移次数
  • 模板题

试计算在区间 1到 n的所有整数中,数字 x(0≤x≤9)共出现了多少次?例如,在 1 到 11 中,即在 1,2,3,4,5,6,7,8,9,10,11 中,数字 1出现了 4次。

模板

image.png

作者:灵茶山艾府

链接:https://leetcode.cn/problems/number-of-digit-one/solutions/1750339/by-endlesscheng-h9ua/

来源:力扣(LeetCode)

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

相关题目

1.数字1的个数【LC233】

2022/11/25

给定一个整数 n,计算所有小于等于 n 的非负整数中数字 1 出现的个数。

image.png

实现

  • 首先将n转化为字符串数组s,存储高位至低位的数值大小,会影响isLimit
  • 然后使用dp数组记录在不受到约束时,第i位枚举值固定时的合法方案数,即从左往右第i位之后所有枚举数中1的个数【不包括第i位】
  • 然后通过for循环,枚举每一位其所有可能的值
  • 举例说明:计算所有小于等于 n 的非负整数中数字 1 出现的个数。

如果当前的枚举数不受限,并且状态dp[i,cnt1]之前已经计算过,那么此时第i位枚举值固定时的合法方案数不需要重新计算,直接返回dp[i,cnt1]即可

class Solution {
    char s[];
    int dp[][];
    public int countDigitOne(int n) {
        s = Integer.toString(n).toCharArray();
        int m = s.length;
        dp = new int[m][m];
        for (int i = 0; i < m; i++) Arrays.fill(dp[i], -1);
        return f(0, 0, true);// 第i位受isLimit约束
    }
    int f(int i, int cnt1, boolean isLimit) {
        if (i == s.length) return cnt1;
        if (!isLimit && dp[i][cnt1] >= 0) return dp[i][cnt1];
        int res = 0; // 记录从左往右第i+1位之后所有枚举数中1的个数
        for (int d = 0, up = isLimit ? s[i] - '0' : 9; d <= up; ++d)
            // d代表枚举要填入的数字 up代表上限值 不受限则为9 受限则为第i位的值
            // 以下代码运用到了回溯
            // 具体含义为:寻找从左往右第i+1为以及之后数位中1的个数,因此需要更新cnt1以及isLimit
            // cnt1含义为前面i位填充1的个数,因此如果当前位填入的数字为i,那么cnt1 + 1,否则加0
            // isLimit含义为该位填充数字是否受限,取决于前一次填充是否受限,以及当前填充是否达到up
            res += f(i + 1, cnt1 + (d == 1 ? 1 : 0), isLimit && d == up);
        if (!isLimit) dp[i][cnt1] = res;// dp数组的含义为在不受到约束时第i位的合法方案数,即从左往右第i位之后所有枚举数中1的个数【不包括第i位】
        return res;
    }
}

复杂度

  • 时间复杂度:O(logn),$\lceil log_{10}n \rceil+1$为数字的位数,时间复杂度 = 状态个数 * 单个状态的转移次数,状态个数就是 dp 数组的长度,即O(logn),而单个状态的转移次数 = O(2) = O(1),所以时间复杂度为O(logn)
  • 空间复杂度:O(logn)

2.不含连续1的非负整数【LC600】

Given a positive integer n, return the number of the integers in the range [0, n] whose binary representations do not contain consecutive ones.

2022/11/25

LC233的思路相同,区别在于把数使用二进制表示

  • 思路:数位dp
    首先将n转换成二进制int数组binary,定义f(i,pre,isLimit,isNum)表示构造从左往右第i位及其之后数位的合法方案数,即不存在连续1的个数,其余参数的含义为:

image.png

实现

  • 首先将n转化为二进制形式,并使用int数组binary存储
  • 然后使用dp[i][j]数组代表在不受到约束时,第i位枚举值固定为j时的,合法方案数,即从左往右第i位之后所有枚举数中不含1的个数【不包括第i位】
  • 然后对于每一位枚举其所有可能的值,由于枚举值只可能为0或者1,因此可以不使用for循环,直接判定
  • 如果不受限并且前一位不为1并且最大值为0,那么第i位可以填充1
  • 任何情况均可以填充0
  • 代码
class Solution {
    int [] binary;
    int[][] dp;
    public int findIntegers(int n) {
        int m = 32;        
        binary = new int[m];// 转化为二进制数组
        for (int i = 0; i < m; i++){
            binary[i] = (n >> (31-i)) & 1;
        }        
        dp = new int[m][2];
        for (int i = 0; i < m; i++){
            Arrays.fill(dp[i],-1);
        }
        int i = 0;
        while (i < m && binary[i] == 0){// 找到第一个不为0的二进制
            i++;
        }
        return f(i,0,true);
    }
    public int f(int i, int pre, boolean isLimit){
        if (i == binary.length){
            return 1;
        }
        if (!isLimit && dp[i][pre] >= 0){// 不受限 并以及计算过
            return dp[i][pre];
        }
        int res = 0;// 记录第i位之后的枚举可能性
        int up = isLimit ? binary[i] : 1;// 
        res += f(i + 1, 0, isLimit && up == 0);// 第i+1位填0 
        if (pre != 1 && up == 1){
            res += f(i + 1, 1, isLimit);// 第i位填1
        }               
        if (!isLimit){
            dp[i][pre] = res;
        }
        return res;
    }    
}

image.png

【题型总结】数位dp(二):https://developer.aliyun.com/article/1405899?spm=a2c6h.13148508.setting.14.45fd4f0eFyVSCn

目录
相关文章
|
算法 开发者
【Makefile 相关 】Makefile中patsubst(扩展通配符)的含义
【Makefile 相关 】Makefile中patsubst(扩展通配符)的含义
429 0
|
存储 关系型数据库 数据库
BTree与B+Tree图文详解
B树与B+树区别
1645 0
BTree与B+Tree图文详解
|
10月前
|
编译器 C语言
【C语言】extern 关键字详解
`extern` 关键字在C语言中用于跨文件共享变量和函数的声明。它允许你在一个文件中声明变量或函数,而在其他文件中定义和使用它们。理解 `extern` 的使用可以帮助你组织和管理大型项目的代码。
977 3
|
10月前
|
搜索推荐 数据挖掘 双11
淘宝运营进阶秘籍:从业余到专业
淘宝运营涉及市场分析、产品定位、店铺装修、营销推广、客户服务、数据分析等多环节。要脱颖而出,需掌握进阶秘籍。本文从精准定位、店铺装修、定价策略、流量获取、客户服务、数据分析及跨平台合作七大方面深入探讨,助商家实现从平凡到卓越的蜕变。通过目标受众分析、优化店铺形象、合理定价、多种促销手段、提升客户体验、利用数据反馈调整策略以及拓展销售渠道,商家可逐步提升运营能力,在竞争激烈的电商环境中取得成功。
1326 4
|
消息中间件 NoSQL Java
使用Java实现分布式任务调度器
使用Java实现分布式任务调度器
|
SQL 搜索推荐 Java
什么是笛卡尔积及其在SQL查询中的应用
什么是笛卡尔积及其在SQL查询中的应用
|
负载均衡 算法 前端开发
Keepalived + Nginx 实现高可用 Web 负载均衡
Keepalived + Nginx 实现高可用 Web 负载均衡
Keepalived + Nginx 实现高可用 Web 负载均衡
|
SQL 分布式计算 Hadoop
Hive的安装与配置——第2关:Hive Shell入门基础命令
Hive的安装与配置——第2关:Hive Shell入门基础命令
1730 0
Hive的安装与配置——第2关:Hive Shell入门基础命令
|
缓存 安全 Java
谈谈springboot的装饰者模式
【4月更文挑战第14天】在 Spring Boot 中,装饰者模式被广泛应用于增强和扩展现有功能,同时保持核心逻辑的清晰和不变。这种模式在处理请求、响应、以及各种中间件中尤为常见,通过包装一个或多个组件来增加额外的行为或修改现有行为
313 2
|
Web App开发 存储 前端开发
❤️使用 HTML、CSS 和 JS 创建在线音乐播放器(含免费完整源码)❤️
❤️使用 HTML、CSS 和 JS 创建在线音乐播放器(含免费完整源码)❤️
1094 0