力扣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),用于记忆化存储。

参考来源

相关推荐
Railshiqian1 小时前
UserPickerActivity 内部逻辑分析
开发语言·python
喜欢打篮球的普通人1 小时前
Trition程序编写:从“Hello CUDA“到“Hello Triton“:向量加法背后的编译黑魔法
开发语言·后端·rust
code_std1 小时前
java WebSocket 使用
java·开发语言·websocket
大鱼>1 小时前
AI+货物追踪:贵重物品智能追踪系统
人工智能·深度学习·算法·机器学习
大鱼>2 小时前
AI+货物追踪:集装箱智能追踪系统
人工智能·深度学习·算法·机器学习
gihigo19982 小时前
FastSLAM2.0(精度优于1.0)MATLAB 实现
开发语言·matlab
Yolo566Q2 小时前
Noah-MP陆面过程模型建模方法与站点、区域模拟实践技术应用
开发语言·python
落叶-IT2 小时前
Java Scanner 类精讲:控制台交互
java·开发语言
z小猫不吃鱼2 小时前
模型剪枝经典论文精读:NISP: Pruning Networks using Neuron Importance Score Propagation
算法·机器学习·剪枝