0
点赞
收藏
分享

微信扫一扫

1012. 至少有 1 位重复的数字 DFS



1012. 至少有 1 位重复的数字

给定正整数 ​​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 <= 10^9​

做题结果

成功 

方法:DFS

首先要知道这个重复的情况很复杂。比如1133,既有1的重复,也有3的重复,这样,我们计算的时候就很容易发生重复。

而且重复的情况,随着位数的增加,比例逐渐增加(20个只有1个,到1000已经有262个,从1/20,增加到1/5),如果在1e9范围内,这个比例是不利的。

而重复的情况,相对容易分辨,一个数字有无包含某个数字,比是否发生重复要容易分。因此我们可以反过来求不重复的情况,再用总数减掉它,注意,由于枚举时,多了一个0,多计算了一个,所以计算到结果时要加回来。

1. 分割数位,分割出每个位置对应10以内的数值

2. 枚举所有情况,进行计数。

3. 去掉重复:使用一个整数标识,对应二进制位标识为0的代表还未使用,用于保证整个拼接数值无重复。

4. 上限:由于给了一个n, 因此我们需要判断是否要根据上线来判断当前数值。什么意思呢?比如给个222,分出 [2,2,2]。如果我第1位填1,那后续可以从0到9中选取。但是如果第 1 位填 2,第 2 位只能选择 0 到 2 的数值。所以这个上限标识,用于区分是否受到原数字对应数位约束

5. 前导 0:由于可以选取位数比目标值位数少的数,所以我们枚举时,可能存在前导 0.那么这个时候发现一个问题,关于 0 的去重不对劲了。比如:0010,这个值它其实是没有重复的。前导 0 不应该影响去重。因此我们需要记录前面是否是前导0,前导0情况,追加的0,不应计入0的去重中。

6. 记忆化: 有的时候,其实后续枚举情况,是前面搜索过的,比如 123? 321?,这两个都是占用了123这三个数,然后枚举后续的数值情况,这种情况是重复的。把前面的几个关键点也包含进来,可设计出这样的二进制格式作为key:10位 visited+1位limit+1位isLeadingZero+4位当前索引值,目前一共16位,小于 int 存储的 32 位,将对应的结果,保存到内存中。找到重复 key 时,直接返回。

class Solution {
public int numDupDigitsAtMostN(int n) {
Deque<Integer> deque = new LinkedList<>();
int v = n;
while (v>0){
deque.offerFirst(v%10);
v = v/10;
}

return n-dfs(new ArrayList<>(deque),0, 0,0,true,true)+1;
}

Map<Integer,Integer> cnts = new HashMap<>();
private int dfs(List<Integer> n, int visited,int curr,int index,boolean limit,boolean isLeadingZero){
if(index==n.size()){
return 1;
}

int key = ((visited*4+(limit?2:0)+(isLeadingZero?1:0))<<4)+index;
if(cnts.containsKey(key)) {
return cnts.get(key);
}
int ans = 0;
int max = limit?n.get(index):9;
for(int i = 0; i <= max; i++){
if(((visited>>i)&1)==0){
if(!isLeadingZero || i!=0) visited = (visited|(1<<i));

ans += dfs(n,visited,curr*10+i,index+1,limit&&i==max,isLeadingZero&&i==0);

if(!isLeadingZero || i!=0) visited = (visited^(1<<i));
}
}
cnts.put(key,ans);
return ans;
}
}

举报

相关推荐

1012. 数字分类

0 条评论