[LeetCode 233] Number of Digit One
Given an integer n, count the total number of digit 1 appearing in all non-negative integers less than or equal to n.
Example:
Input: 13 Output: 6 Explanation: Digit 1 occurred in the following numbers: 1, 10, 11, 12, 13.
If n is not big, then we can just count each number's digit 1 and add them up. However, if n can be as big as 10^9, this approach takes too long. We need to solve it by first dividing the original problem into the following smaller problems.
Let's say the given input integer n has K digits. Then all the digit 1 comes from : (a) all numbers that have < K digits; (b) all numbers that have K digits and <= n.
To compute a, we use this formula: Cnt_P_Digits = P * 10^(P - 1) - Cnt_P - 1_Digits, Cnt_0_Digits = 0. It is easy to see that for a given digit count P, we can try to fix a 1 on one digit position, then count the different numbers with this position fixed. This will be P * 10^(P - 1) in total before we deduct the invalid leading 0 numbers' contributions. If we set the leading digit to 0, the contribution becomes a smaller subproblem with one fewer digit, which is Cnt_P - 1_Digits.
To compute b, we use the same fix then count strategy with a. Denote all digits of n as d[i] and there are K digits in total. We have the following 3 cases:
1. current digit d[i] is 0, setting this digit to 1 makes n bigger, we must make the leading part smaller to compensate for this increase. This means the left part can be any number from 10^(i - 1) to d[0, i - 1] - 1. Since the leading part is < d[0, i - 1], the right part can be anything from 000... to 10^(K - 1 - i).
2. d[i] == 1, first do the same with 1 to count all the contributions with a smaller left part; Then we need to count the remaining contributions from numbers with the same leading left part. In this case, the right part can only go up to d[i, ...... K - 1] from 00, otherwise the numbers will be > n.
3. d[i] > 1, since fixing this digit to 1 makes n smaller, the left part can be from 10^(i - 1) to d[0, i - 1]. The right part can be anything from 000... to 10^(K - 1 - i).
The following code implements the above algorithm. The runtime is O(log N * log N). It takes O(log N) time to loop through all digits. For each iteration it takes O(log N) time to compute left and right. A better way shown in the 2nd implementation is to use a sliding window that gives O(1) time to compute left and right. This makes the overall runtime to be O(log N).
class Solution { public int countDigitOne(int n) { if(n <= 0) { return 0; } List<Integer> digits = new ArrayList<>(); while(n > 0) { digits.add(n % 10); n /= 10; } Collections.reverse(digits); int cnt = 0, prevCnt = 0; int[] base = new int[10]; base[0] = 1; for(int i = 1; i < 10; i++) { base[i] = base[i - 1] * 10; } for(int i = 1; i < digits.size(); i++) { cnt += (i * base[i - 1] - prevCnt); prevCnt = cnt; } for(int i = 0; i < digits.size(); i++) { int left = 0, right = 0; for(int j = 0; j < i; j++) { left = left * 10 + digits.get(j); } for(int j = i + 1; j < digits.size(); j++) { right = right * 10 + digits.get(j); } if(digits.get(i) <= 1) { cnt += Math.max(left - 1 - (i > 0 ? base[i - 1] : 0) + 1, 0) * base[digits.size() - 1 - i]; if(digits.get(i) == 1) { cnt += (right + 1); } } else { cnt += (left - (i > 0 ? base[i - 1] : 0) + 1) * base[digits.size() - 1 - i]; } } return cnt; } }
class Solution { public int countDigitOne(int n) { if(n <= 0) { return 0; } List<Integer> digits = new ArrayList<>(); while(n > 0) { digits.add(n % 10); n /= 10; } Collections.reverse(digits); int cnt = 0, prevCnt = 0; int[] base = new int[10]; base[0] = 1; for(int i = 1; i < 10; i++) { base[i] = base[i - 1] * 10; } for(int i = 1; i < digits.size(); i++) { cnt += (i * base[i - 1] - prevCnt); prevCnt = cnt; } int left = 0, right = 0; for(int i = 1; i < digits.size(); i++) { right = right * 10 + digits.get(i); } for(int i = 0; i < digits.size(); i++) { left = (i > 0 ? left * 10 + digits.get(i - 1) : 0); right = right % base[digits.size() - 1 - i]; if(digits.get(i) <= 1) { cnt += Math.max(left - 1 - (i > 0 ? base[i - 1] : 0) + 1, 0) * base[digits.size() - 1 - i]; if(digits.get(i) == 1) { cnt += (right + 1); } } else { cnt += (left - (i > 0 ? base[i - 1] : 0) + 1) * base[digits.size() - 1 - i]; } } return cnt; } }