剑指 Offer 之 从整数 1 到 n 中 1 出现的次数
Contents
该题是剑指 Offer 中一道考验分析数字规律的题,在 LeetCode 上看到一种十分巧妙的方法,为此在这里记录一下,以便日后复习。
题目描述
求出任意非负整数区间中 1 出现的次数,即在 1 到 n 中,1 所出现的次数。
分析
方法一
最能想到的方法就是累加 1 到 n 中每个整数 1 出现的次数,如果当前数字大于 10,则可以对 10 求余的方式判断该数的个位数字是不是 1,不断的累加直到输出最后结果。
当然这种方式的时间复杂度很高,为 O(nlogn),通常也不是面试官想要的答案,他会问你有没有更好的方法?
方法二
我们可以利用 1、10、100、1000……这样的数来作为输入数字 n 划分的依据,例如输入的数字是 21396,我们可以考虑个位、十位、百位、千位……的划分情况,这里以 百位 为例,由于百位上的数字范围是从 0 到 9,所以可以将其分为 百位数字大于等于 2 、百位数字等于 1 以及 百位数字等于 0 三种情况,下面分别讨论:
情况一
对于 百位数字大于等于 2 的情况,如数字 21396,则有以下情况:
小技巧:对于
x % y操作,运算结果会在0 ~ (y - 1)之间。
例如
123 % 5 = 4,其结果一定在0 ~ 4之间,不管是123还是像1231241这样多大的数,经过取余运算,其结果都会在0 ~ 4之间的。
| 序号 | 对应的情况 |
|---|---|
| 1 | 100 ~ 199 |
| 2 | __1__100 ~ __1__199 |
| 3 | __2__100 ~ __2__199 |
| 4 | __3__100 ~ __3__199 |
| 5 | __4__100 ~ __4__199 |
| 6 | __5__100 ~ __5__199 |
| 7 | __6__100 ~ __6__199 |
| 8 | __7__100 ~ __7__199 |
| 9 | __8__100 ~ __8__199 |
| … | … |
| 20 | __19__100 ~ __19__199 |
| 21 | __20__100 ~ __20__199 |
| 22 | __21__100 ~ __21__199 |
| 剩余情况则不满足 | – |
为了便于计算,先得到
a = 21396 / 100 = 213,b = 21396 % 100 = 96。
所以,对于 百位数字大于等于 2 的情况,共有 (a / 10 + 1) * 100 = 2200 个数字是含有 1 的。也就是说,在 (213 / 10 + 1) = 22 的情况下,每种情况有 0 ~ 99 个数。
情况二
对于 百位数字等于 1 的情况,如数字 21196,则有以下情况:
| 序号 | 对应的情况 |
|---|---|
| 1 | 100 ~ 199 |
| 2 | __1__100 ~ __1__199 |
| 3 | __2__100 ~ __2__199 |
| 4 | __3__100 ~ __3__199 |
| 5 | __4__100 ~ __4__199 |
| 6 | __5__100 ~ __5__199 |
| 7 | __6__100 ~ __6__199 |
| 8 | __7__100 ~ __7__199 |
| 9 | __8__100 ~ __8__199 |
| … | … |
| 20 | __19__100 ~ __19__199 |
| 21 | __20__100 ~ __20__199 |
| 22 | __21__100 ~ __21__196 |
对于这种情况,共有 (a / 10) * 100 + (b + 1) = 2197 个数字是含有 1 的。注意序号 22 对应的就是 (b + 1) 这部分,我们将其单独计算,即 (96 + 1) = 97。
情况三
对于 百位数字等于 0 的情况,如数字 21096,则有以下情况:
| 序号 | 对应的情况 |
|---|---|
| 1 | 100 ~ 199 |
| 2 | __1__100 ~ __1__199 |
| 3 | __2__100 ~ __2__199 |
| 4 | __3__100 ~ __3__199 |
| 5 | __4__100 ~ __4__199 |
| 6 | __5__100 ~ __5__199 |
| 7 | __6__100 ~ __6__199 |
| 8 | __7__100 ~ __7__199 |
| 9 | __8__100 ~ __8__199 |
| … | … |
| 20 | __19__100 ~ __19__199 |
| 21 | __20__100 ~ __20__199 |
| 22(该情况不满足) |
注意序号 22,该情况大于了给定的数字 21096,所以不考虑,将其划掉。
则这种情况下共有 (a / 10) * 100 = 2100 个数字是含有 1 的。
情况汇总
可以看到,对于 百位数字大于等于 2 与 百位数字等于 0 的情况是可以结合起来的,也就是 (a + 8) / 10 * 100;而对于 百位数字等于 1 的情况,可以用 (a + 8) / 10 * 100 + (b + 1) 表示。
之所以补上 8,是因为当百位为 0 时,则有 a / 10 == (a + 8) / 10;当百位大于等于 2 时,补 8 会产生进位,等价于 (a / 10 + 1)。
代码
|
|
参考
https://leetcode.com/problems/number-of-digit-one/discuss/64381/4+-lines-O(log-n)-C++JavaPython https://www.nowcoder.com/questionTerminal/bd7f978302044eee894445e244c7eee6?f=discussion