【数位DP】
【数位DP】
对数字位数,把每一位拆开、关注每一位数字
特征
1.最终目的为计数:统计满足一定条件的数的数量
2.问题经过转化后,可以使用 「数位」 的思想去理解和判断
3.输入:提供数字区间/上界 来作为统计的限制
4.上界很大(\(10^{18}\))
思考方式
把相似的计数过程进行归并->这些过程产生的计数答案存在一个通用数组里
通常会使用常见计数技巧:
eg 前缀和计数:\(ans_{[l,r]}=ans_{[0,r]}-ans_{[0,l-1]}\)
【统计答案】 记忆化搜索/循环迭代递推
从高到低枚举每一位,再考虑每一位都可以填哪些数字,最后利用通用答案数组统计答案。
例题
题目积累
最大为 N 的数字组合
https://leetcode.cn/problems/numbers-at-most-n-given-digit-set/description/
题目描述
思路
三种情况
(1)位数少
(2)[小]xxx
(3)[相同数/小][相同数/小][相同数/小]xxx
法一
class Solution {
public:
int atMostNGivenDigitSet(vector<string>& digits, int n) {
vector<int> num(digits.size(),0);
int cnt=0;
for(auto digit : digits){
num[cnt++]=digit[0]-'0';
}
//对n进行操作:方便每一位提取数字
int tmp=n/10;
int len=1;
int offset=1;//方便提取n中某一位数字:辅助len
while(tmp>0){
tmp/=10;
len++;
offset*=10;
}
return f1(num,n,offset,len,0,0);
}
/*
【思路一:递归】
还剩下len位没决定
free
(1)如果之前的位比num小->free==1 接下来的数可以自由选择
(2)如果之前的位比num一样->free==0 接下来的数不能大于num当前位
fix
(1)之前的位没有使用过数字->fix==0
(2)之前的位有使用过数字->fix==1
*/
int f1(vector<int> digits,int num,int offset,int len,int free,int fix){
if(len==0){
return fix==1?1:0;
}
int ans=0;
//num在当前位的数字
int cur=(num/offset)%10;
if(fix==0){
//之前从来没有选过任何数字
//当前依然不需要任何数字,考虑后续可能性
ans+=f1(digits,num,offset/10,len-1,1,0);
}
if(free==0){
//不能自由选择
for(int i:digits){
if(i<cur){
ans+=f1(digits,num,offset/10,len-1,1,1);
}
else if(i==cur){
ans+=f1(digits,num,offset/10,len-1,0,1);
}
else{//i>cur 因为digits有序
break;
}
}
}
else{
//可以自由选择
ans+=digits.size()*f1(digits,num,offset/10,len-1,1,1);
}
return ans;
}
};
法二
对于已经确定小于的情况->可以任选->直接打表计算
class Solution {
public:
int atMostNGivenDigitSet(vector<string>& digits, int n) {
vector<int> num(digits.size(),0);
int cntt=0;
int m=digits.size();
for(auto digit : digits){
num[cntt++]=digit[0]-'0';
}
//对n进行操作:方便每一位提取数字
int tmp=n/10;
int len=1;
int offset=1;//方便提取n中某一位数字:辅助len
while(tmp>0){
tmp/=10;
len++;
offset*=10;
}
//前缀已经小了 后续可以直接加上去 不用递归算了
//cnt[i]:已知前缀比num小,剩下i位没有确定,在前缀确定的情况下,有多少种数字排列
//cnt[0]=1 后续没有,前缀状况确定
//cnt[1]=m
//cnt[2]=m*m
//cnt[3]=m*m*m
//...
vector<int> cnt(20,0);
cnt[0]=1;
int ans=0;
for(int i=m,k=1;k<len;k++,i*=m){
cnt[k]=i;
ans+=i;
}
return ans+=f2(num,cnt,n,offset,len);
}
int f2(vector<int> digits,vector<int> cnt,int num,int offset,int len){
if(len==0){
return 1;
}
int cur=(num/offset)%10;
int ans=0;
for(auto digit:digits){
if(digit<cur){//后面可以任意选->直接加
ans+=cnt[len-1];
}
else if(digit==cur){
ans+=f2(digits,cnt,num,offset/10,len-1);//往下接着找
}
else break;
}
return ans;
}
};