Given an integer n, count the total number of digit 1 appearing in all non-negative integers less than or equal to n.
For example:
Given n = 13,
Return 6, because digit 1 occurred in the following numbers: 1, 10, 11, 12, 13.
以前应该做过类似的题,最早好像是在一次笔试里做到的。先求出[0,9],[0,99],[0,999]这些规整范围内的1的个数。然后对于一个数比如2132就可以把它拆分成
这么几个部分,前面两个非常规整可以直接用已经计算好的[0, 999]中的1的个数直接算出。即看做两个分别由0打头和1打头的[0,999]范围,当然因为在1打头的情况下,其范围内每个数都贡献了一个开头的1要进行特殊考虑。第三个区间(如果打头的不是1)则等效与求[0,132]范围内的1的个数,此时我们已经将问题缩小了,不断进行这个分解过程直到个位为止。下面是代码
class Solution {
private:
int mag[20];
public:
int countDigitOne(int n) {
for (int i=0; i<20; i++) {
mag[i] = -1;
}
mag[1] = 1;
int pw = 1;
int t = n;
int mg = 1;
while (t > 9) {
t/=10;
pw*=10;
mg++;
}
return dfs(n, pw, mg);
}
int dfs(int n, int pw, int mg) {
if (pw < 2) {
return n >= 1 ? 1 : 0;
}
int d = n / pw;
int r = n % pw;
int cnt = 0;
for (int i=0; i<d; i++) {
cnt += mcount(mg - 1);
}
if (d > 1) {
cnt += pw;
} else if (d == 1) {
cnt += r + 1;
}
return cnt + dfs(r, pw / 10, mg - 1);
}
int mcount(int m) {
if (m < 0) {
return 0;
}
if (mag[m] > 0) {
return mag[m];
}
int pw = 1;
for (int i=1; i<m; i++) {
pw *= 10;
}
return mag[m] = (mcount(m-1) * 10 + pw);
}
};
代码里递归都是尾递归,可以写成迭代形式,懒得改了。
果然自己的方法无比挫逼,看到这个之后:
class Solution {
public:
int countDigitOne(int n) {
long pw = 1;
int cnt = 0;
while (n / pw > 0) {
int r = n % pw;
int d = (n / pw) % 10;
int h = (n / pw) / 10;
switch (d) {
case 0:
cnt += h * pw;
break;
case 1:
cnt += h * pw + r + 1;
break;
default:
cnt += (h + 1) * pw;
}
pw *= 10;
}
return cnt;
}
};
其实这个switch里判断可以合并成一两条语句,不过还是这样放着最好理解,没必要装逼。注意pw可能会溢出,如果一定全用int类型的话,可以和INT_MAX/10做比较。
这样整个过程按数字位遍历即可得出结果,如一个数2134
可以这样考虑: