力扣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),其中 mN 的位数(最大为10)。状态数为 m * 2^10,每个状态最多枚举 10 个数字。
  • 空间复杂度:O(m * 2^10),用于记忆化存储。

参考来源

相关推荐
啦啦啦啦啦zzzz9 分钟前
Logger的组装(c++20)
c++·算法·c++20
Tangyuewei18 分钟前
Java 写 Agent:模型只是组件
java·开发语言
小玮看世界1 小时前
[Python]从合并区间到传感器融合区:合并区间在传感器区域融合的实际落地
开发语言·python
尾善爱看海1 小时前
前端算法与手写题集
前端·算法
繁星蓝雨2 小时前
C++的设计与演进大纲(如何从C语言一步步成长为C++)
c语言·开发语言·c++·c++历史·c++演进
程序员阿鹏2 小时前
为什么MySQL InnoDB选择B+树?
数据结构·数据库·b树·sql·mysql·算法·缓存
Evand J2 小时前
【MATLAB例程,图像降噪滤波9】自适应维纳滑动窗口滤波(Wiener)图像降噪、方法对比,有中文注释
开发语言·图像处理·计算机视觉·matlab·滑动窗口·滤波·降噪
蒸蒸yyyyzwd2 小时前
机试学习笔记
c++·求职招聘
AI 思录2 小时前
Prompt 事故档案(八):日常表达被标为“待校准”,AI 的爹味语法从哪里来
大数据·人工智能·算法·prompt·用户体验·ai合规
月光船幽幽2 小时前
跨范式映射的稳定接口设计
人工智能·python·算法