[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;
    }
}

 










posted @ 2020-08-11 00:21  Review->Improve  阅读(220)  评论(0编辑  收藏  举报