{x}
blog image

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 1:

Input: n = 13
Output: 6

Example 2:

Input: n = 0
Output: 0

 

Constraints:

  • 0 <= n <= 109

Solution Explanation: Counting Digit One

This problem requires counting the occurrences of the digit '1' in all non-negative integers up to a given integer n. A brute-force approach would be inefficient for large values of n. The optimal solution employs a dynamic programming technique known as Digit DP.

Core Idea:

Digit DP efficiently solves problems involving counting numbers with specific properties within a range. Instead of iterating through every number, it cleverly breaks down the problem into smaller subproblems based on the digits of the numbers.

Algorithm (Digit DP):

  1. Convert to String: The input integer n is converted into a string s for easier digit-by-digit processing.

  2. Recursive Function dfs(i, cnt, limit): This function is the heart of the Digit DP solution.

    • i: The current digit index being processed (starting from the most significant digit).
    • cnt: The accumulated count of '1's encountered so far.
    • limit: A boolean flag indicating whether the current digit is constrained by the upper bound n. If limit is true, the current digit cannot exceed the corresponding digit in n.
  3. Base Case: When i reaches the end of the string (i >= len(s)), the accumulated count cnt is returned.

  4. Recursive Step: The function iterates through possible digits (j) from 0 to up, where up is either 9 (if limit is false) or the corresponding digit in n (if limit is true).

    • If j is 1, the cnt is incremented.
    • The function recursively calls itself with i + 1, the updated cnt, and a new limit value (limit && j == up). The new limit ensures that subsequent digits remain within the bounds of n.
  5. Memoization: To avoid redundant calculations, the dfs function utilizes memoization (caching). The results of subproblems are stored to speed up subsequent calls.

  6. Initial Call: The function is initially called with dfs(0, 0, True). This starts the process from the most significant digit, with an initial count of 0 and limit set to true (since the first digit must be within the bounds of n).

Time and Space Complexity:

  • Time Complexity: O(log₁₀(n)), or O(m) where m is the number of digits in n. The recursive function explores a tree-like structure with a depth proportional to the number of digits. Memoization significantly reduces the actual runtime by caching intermediate results.

  • Space Complexity: O(log₁₀(n)) or O(m) for the recursion stack and the memoization table. In the worst case, the recursion depth is proportional to the number of digits, and the memoization table stores results for subproblems.

Code Examples (Python, Java, C++, Go, TypeScript, C#):

The code examples provided in the original response demonstrate the Digit DP algorithm in various programming languages. They all follow the same algorithmic structure, differing only in syntax and specific language features like memoization implementation.

Example Walkthrough (n = 13):

Let's trace the execution for n = 13:

  1. s = "13"
  2. dfs(0, 0, True) is called.
  3. For i = 0, up = 1. The loop iterates from j = 0 to j = 1.
    • j = 0: dfs(1, 0, True) is called.
    • j = 1: dfs(1, 1, True) is called.
  4. This continues recursively until the base case is reached. The final result is correctly calculated as 6.

The Digit DP approach offers an efficient solution to this problem, avoiding the need for explicit iteration through all numbers up to n. The memoization optimization drastically improves performance for larger inputs.