题意:给定一个整数n,统计[1,n]这个区间之间每一位数都不同的整数共有多少个。
思路:每一位共有10中选择,如果暴力搜索的话那么就是10^n,这样的复杂度显然不能接受。
一种好的方法是采用数位dp。数位dp通常用于求解一个区间内满足条件的数的总数,这个区间通常情况下非常大,不适合暴力搜索。
用一个状态位status来表示0-9之间的数字是否被选中,例如 1100000001,表示0、8、9这三个数字已经被选中,所以要有一个参数pos来记录当前的位置。这两个参数就帮助我们记忆化了之前搜索过的。
现在我们还需要第3个参数limit,这个参数表示当前位可选的数字是否收到上一位的限制,例如,对于数字1234,如果第3位选择的数字是3,那么第4位可选的数字就是0-4,否则就是0-9。
最后,在一些题目中可能会出现前导0的限制,也就是说数字不能以0开头。对于这种情况,可以单独处理。
class Solution {
public:
/*
数位dp
*/
vector<int> num;
int dp[11][1 << 10];
int len;
int countSpecialNumbers(int n) {
memset(dp, -1, sizeof(dp));
len = n;
while(n){
num.push_back(n%10);
n/=10;
}
return dps(num.size()-1, 0, true);
}
int dps(int pos, int status, bool limit){
if(pos < 0) return 1;
if(!limit && dp[pos][status]!=-1) return dp[pos][status];
int res = 0;
int mx = limit ? num[pos] : 9;
for(int i = 0; i <= mx; i++){
if(pos == 0 && status == 0 && i ==0) continue; //去除一直为0的情况
else if(status == 0 && i == 0) //单独处理前导0,不加入status中
res += dps(pos-1, status, limit && num[pos] == i);
else if(! (status & (1 << i)) )
res += dps(pos-1, status | (1 << i), limit && i == num[pos]);
}
if(!limit) dp[pos][status] = res;
return res;
}
};
数位dp的时间复杂度等于 状态个数*转移个数,对于本题就是 O(2^n * n),n是数组num的长度。