【1012. 至少有 1 位重复的数字】
创始人
2025-05-31 15:23:45
0

来源:力扣(LeetCode)

描述:

给定正整数 n,返回在 [1, n] 范围内具有 至少 1 位 重复数字的正整数的个数。

示例 1:

输入:n = 20
输出:1
解释:具有至少 1 位重复数字的正数(<= 20)只有 11 。

示例 2:

输入:n = 100
输出:10
解释:具有至少 1 位重复数字的正数(<= 100)有 11,22,33,44,55,66,77,88,99 和 100 。

示例 3:

输入:n = 1000
输出:262

提示:

  • 1 <= n <= 109

方法一:记忆化搜索

由互斥原理可知,至少有 1 位重复的数字的正整数个数等于总个数减去没有重复数字的正整数个数。为了方便计算,我们首先求出在整数区间 [0, n] 之间的没有重复数字的正整数个数 x,那么结果等于 n + 1 − x。

我们从最高位开始填入各个数字,使用整数掩码 mask 记录前面已经填入过的数字(注意前缀 0 不计入已填入的数字)。假设当前填入第 i 位,如果前面填入的数字与 n 对应位置的数字相同,那么可选的填入数字小于等于 n 在第 i 位的数字,否则可填入全部数字。

记可填入的最大数字为 t,依次尝试填入数字 k ∈ [0, t],如果 k 已经出现在 mask 中,那么说明填入数字 k 不合法,否则说明可以填入数字 k,那么尝试填入第 i + 1 位的数字。

在填入第 i + 1 位的数字时,如果掩码 maski = 0 且 k = 0 成立,那么说明前面都是前缀 0,掩码 maski+1 为 0,否则 maski+1 等于 maski 在第 k 位设为一后的值。如果在填入第 i 位时,前面填入的数字与 n 对应位置的数字相同,且在第 i 位填入的数字为 t,那么填入第 i + 1 位时,前面填入的数字也与 n 对应位置的数字相同。

注意到,假设当前需要填入第 i 位,且前面填入的数字与 n 对应位置的数字不相同,那么需要求得的不重复数字的正整数个数只与 mask 相关,我们可以使用备忘录 dp 记录该结果,避免重复计算。

代码:

class Solution {
public:vector> dp;int f(int mask, const string &sn, int i, bool same) {if (i == sn.size()) {return 1;}if (!same && dp[i][mask] >= 0) {return dp[i][mask];}int res = 0, t = same ? (sn[i] - '0') : 9;for (int k = 0; k <= t; k++) {if (mask & (1 << k)) {continue;}res += f(mask == 0 && k == 0 ? mask : mask | (1 << k), sn, i + 1, same && k == t);}if (!same) {dp[i][mask] = res;}return res;}int numDupDigitsAtMostN(int n) {string sn = to_string(n);dp.resize(sn.size(), vector(1 << 10, -1));return n + 1 - f(0, sn, 0, true);}
};

执行用时:8 ms, 在所有 C++ 提交中击败了74.55%的用户
内存消耗:9.8 MB, 在所有 C++ 提交中击败了14.54%的用户
复杂度分析
时间复杂度:O(m×2w),其中 m 是整数 n 的十进制位数,w = 10 表示十进制数的数字类型数目。最多计算 m×2w 个状态,单个状态需要 O(w) 的时间。
空间复杂度:O(m×2w)。保存 dp 需要 O(m×2w) 的空间。

方法二:组合数学

方法一只有两种情况需要继续进入搜索:

  • 前面填入的数字与 n 对应位置的数字相同,且当前填入的数字为 t;
  • 前面填入的数字都是 0,即前缀 0,并且当前填入的数字也为 0。

其他情况可以直接利用组合数学进行计算,当前填入第 i 位,那么剩余 m − 1 − i 位待填入,记已经填入的数字数目为 c,那么可选的数字数目为 10 − c,那么剩余的不重复数字的正整数个数等于组合数 A10−ccA_{10-c}^cA10−cc​(n 的数据范围保证了组合数合法)。

代码:

class Solution {
public:int A(int x, int y) {int res = 1;for (int i = 0; i < x; i++) {res *= y--;}return res;}int f(int mask, const string &sn, int i, bool same) {if (i == sn.size()) {return 1;}int t = same ? sn[i] - '0' : 9, res = 0, c = __builtin_popcount(mask) + 1;for (int k = 0; k <= t; k++) {if (mask & (1 << k)) {continue;}if (same && k == t) {res += f(mask | (1 << t), sn, i + 1, true);} else if (mask == 0 && k == 0) {res += f(0, sn, i + 1, false);} else {res += A(sn.size() - 1 - i, 10 - c);}}return res;}int numDupDigitsAtMostN(int n) {string sn = to_string(n);return n + 1 - f(0, sn, 0, true);}
};

执行用时:0 ms, 在所有 C++ 提交中击败了100.00%的用户
内存消耗:5.7 MB, 在所有 C++ 提交中击败了98.18%的用户
复杂度分析
时间复杂度:O(m×w2),其中 m 是整数 n 的十进制位数,w=10 表示十进制数的数字类型数目。计算组合数需要 O(w) 的时间,而前面提到的两种情况互斥,因此搜索过程最多只出现一种情况,搜索层数为 m 层,因此总时间复杂度为 O(m×w2)。
空间复杂度:O(m)。递归需要 O(m) 的栈空间。
author:LeetCode-Solution

相关内容

热门资讯

喜欢穿一身黑的男生性格(喜欢穿... 今天百科达人给各位分享喜欢穿一身黑的男生性格的知识,其中也会对喜欢穿一身黑衣服的男人人好相处吗进行解...
发春是什么意思(思春和发春是什... 本篇文章极速百科给大家谈谈发春是什么意思,以及思春和发春是什么意思对应的知识点,希望对各位有所帮助,...
网络用语zl是什么意思(zl是... 今天给各位分享网络用语zl是什么意思的知识,其中也会对zl是啥意思是什么网络用语进行解释,如果能碰巧...
为什么酷狗音乐自己唱的歌不能下... 本篇文章极速百科小编给大家谈谈为什么酷狗音乐自己唱的歌不能下载到本地?,以及为什么酷狗下载的歌曲不是...
华为下载未安装的文件去哪找(华... 今天百科达人给各位分享华为下载未安装的文件去哪找的知识,其中也会对华为下载未安装的文件去哪找到进行解...
家里可以做假山养金鱼吗(假山能... 今天百科达人给各位分享家里可以做假山养金鱼吗的知识,其中也会对假山能放鱼缸里吗进行解释,如果能碰巧解...
四分五裂是什么生肖什么动物(四... 本篇文章极速百科小编给大家谈谈四分五裂是什么生肖什么动物,以及四分五裂打一生肖是什么对应的知识点,希...
怎么往应用助手里添加应用(应用... 今天百科达人给各位分享怎么往应用助手里添加应用的知识,其中也会对应用助手怎么添加微信进行解释,如果能...
一帆风顺二龙腾飞三阳开泰祝福语... 本篇文章极速百科给大家谈谈一帆风顺二龙腾飞三阳开泰祝福语,以及一帆风顺二龙腾飞三阳开泰祝福语结婚对应...
美团联名卡审核成功待激活(美团... 今天百科达人给各位分享美团联名卡审核成功待激活的知识,其中也会对美团联名卡审核未通过进行解释,如果能...