题意: 给定一个N,求1 - N中,输出至少有一位数字是重复的数字的个数.
思路: 很明显, 这道题要用数位dp, 考虑两点, 一是前导零的影响, 二是记录的应该是每一位的次数, 而不能是简单的标记和清空标记, 比如101 和 110. 所以状态想好为dp[i][j][k] 表示起始位i, 当前位j, 是否已经有重复位数k(0 or 1) , 然后做记忆化搜索就行, 没啥坑点.
AC Code
typedef long long ll;
ll dp[20][20][2], shu[20], vis[20];
ll dfs(int st, int cur, int id, int state, int limit) {
// st 起始位, cur 当前位, id 上一位填的数字, state 是否已经满足, limit最高位限制
++ vis[id];
if (cur < 1) {
-- vis[id];
return state;
}
if (!limit && dp[st][cur][state] != -1) {
-- vis[id];
return dp[st][cur][state];
}
int up = limit ? shu[cur] : 9;
ll res = 0;
for (int i = 0 ; i <= up ; ++ i) {
if (st == cur && !i)
res += dfs(st-1, cur-1, id, state, limit && i == up);
else if (state)
res += dfs(st, cur-1, i, state, limit && i == up);
else res += dfs(st, cur-1, i, bool(vis[i]), limit && i == up);
}
if (!limit) dp[st][cur][state] = res;
-- vis[id];
return res;
}
ll cal(ll x) {
int k = 0;
while(x) {
shu[++k] = x % 10;
x /= 10;
}
return dfs(k, k, 15, 0, 1);
}
class Solution {
public:
int numDupDigitsAtMostN(int N) {
memset(dp, -1, sizeof(dp));
return cal(N);
}
};