力扣1012数位DP解法详解

问题本质 :计算 [1, N] 范围内至少有一位重复数字 的正整数个数。直接计算较难,可转化为计算没有重复数字 的数的个数(即各位数字互不相同的数),然后用总数 N 减去该值 。

核心思路 :计算 [1, N] 范围内数位互不相同的数的个数。

  1. 数位 DP 状态设计 :dp[pos][mask][isLimit][isNum]
    • pos: 当前处理到数字的第几位(从高位到低位)。
    • mask: 一个位掩码(10位),记录 0~9 哪些数字已被使用过。
    • isLimit: 当前位是否受到 N 对应位的限制(即之前所有位都与 N 相同)。
    • isNum: 当前是否已经开始构造数字(避免前导零被误判为使用数字0)。
  2. 记忆化搜索 :从最高位开始递归填充每一位,在 !isLimit && isNum 时缓存结果。
  3. 最终答案 :N - countUnique(N)。

C++ 代码实现:

cpp 复制代码
class Solution {
public:
    int numDupDigitsAtMostN(int n) {
        string s = to_string(n);
        int m = s.length();
        // dp[pos][mask] 记忆化,isLimit=false 且 isNum=true 时才缓存 vector<vector<int>> memo(m, vector<int>(1 << 10, -1));
        
        // 返回从 pos 开始构造,已使用数字掩码为 mask,能构造出的无重复数字的数的个数
        // isLimit: 当前位是否受 n 的限制 // isNum:是否已经开始构造数字(即是否跳过了前导零)
        function<int(int, int, bool, bool)> dfs = [&](int pos, int mask, bool isLimit, bool isNum) -> int {
            if (pos == m) {
                // 构造完成,如果已经开始构造数字(isNum=true),则算一个有效数字 return isNum ? 1 : 0;
            }
            // 记忆化:只有在不受限制且已经开始构造数字时,结果才是可复用的 if (!isLimit && isNum && memo[pos][mask] != -1) {
                return memo[pos][mask];
            }
            int res = 0;
            // 如果之前还没有开始构造数字,这一位可以选择继续跳过(即仍然不构造数字)
            if (!isNum) {
                // 跳过当前位,即这一位不填数字,mask 不变,isLimit=false(因为跳过了,相当于填了0,但0不计入数字,所以不受原数限制)
                res += dfs(pos + 1, mask, false, false);
            }
            // 确定当前位可以填的数字范围
            int low = isNum ? 0 : 1; // 如果还没开始构造,不能填0(避免前导零)
            int high = isLimit ? s[pos] - '0' : 9;
            // 枚举当前位可以填的数字
            for (int d = low; d <= high; ++d) {
                // 检查数字 d 是否已经被使用过 if ((mask >> d) & 1) continue; // 被使用过,跳过 // 填入数字 d,更新 mask,更新 isLimit(如果之前受限且当前位填的是上限值,则后续仍受限)
                res += dfs(pos + 1, mask | (1 << d), isLimit && (d == high), true);
            }
            // 缓存结果:只有不受限制且已经开始构造数字的状态才缓存 if (!isLimit && isNum) {
                memo[pos][mask] = res;
            }
            return res;
        };
 // 总共有 n 个数,减去没有重复数字的数的个数,即为至少有一位重复的数字的个数 return n - dfs(0, 0, true, false);
    }
};

复杂度分析:

  • 时间复杂度 :O(m * 2^10 *10),其中 m 是 N 的位数(最大为10)。状态数为 m * 2^10,每个状态最多枚举 10 个数字。
  • 空间复杂度:O(m * 2^10),用于记忆化存储。

参考来源

相关推荐
java资料站3 小时前
十一、评估测试
开发语言·人工智能·python
左手の明天4 小时前
SLIP 协议封装与解析详解:原理、C 代码与实战案例
linux·c语言·开发语言·c++
海上小飞龙4 小时前
【KMP算法-下篇】同一道题:Java 库函数 2600 微秒,手写 KMP 16 微秒
java·开发语言·算法
ychqsq4 小时前
203.诊断
经验分享·职场和发展
青少儿编程课堂4 小时前
后缀自动机解析:本质不同子串与最长重复片段统计
c++·python·算法·bfs·信息学竞赛
朝朝辞暮i4 小时前
C++ 第 12 课:局部变量、作用域、变量生命周期
开发语言·c++·算法
狗凯之家源码网4 小时前
淘客口令落地页生成器 PHP 版深度评测
开发语言·php
画绛集美术5 小时前
本地化部署LLM用于美术史问答:一间美术教室的实践记录
开发语言·php
八解毒剂5 小时前
【计组】中央处理器CPU
java·开发语言·计算机组成原理
乐迪信息6 小时前
港口船舶逆行怎么管?AI防爆摄像机搭载智能检测算法
大数据·人工智能·深度学习·算法·计算机视觉