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
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):
Convert to String: The input integer n
is converted into a string s
for easier digit-by-digit processing.
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
.Base Case: When i
reaches the end of the string (i >= len(s)
), the accumulated count cnt
is returned.
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).
j
is 1, the cnt
is incremented.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
.Memoization: To avoid redundant calculations, the dfs
function utilizes memoization (caching). The results of subproblems are stored to speed up subsequent calls.
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:
s = "13"
dfs(0, 0, True)
is called.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.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.