Skip to content

Commit db461b7

Browse files
committed
graph
1 parent 6153d4a commit db461b7

14 files changed

Lines changed: 1183 additions & 308 deletions

File tree

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# https://leetcode.com/problems/rotated-digits/description/
2+
3+
'''
4+
An integer x is a good if after rotating each digit individually by 180 degrees, we get a valid number that is different from x. Each digit must be rotated - we cannot choose to leave it alone.
5+
6+
A number is valid if each digit remains a digit after rotation. For example:
7+
8+
0, 1, and 8 rotate to themselves,
9+
2 and 5 rotate to each other (in this case they are rotated in a different direction, in other words, 2 or 5 gets mirrored),
10+
6 and 9 rotate to each other, and
11+
the rest of the numbers do not rotate to any other number and become invalid.
12+
Given an integer n, return the number of good integers in the range [1, n].
13+
14+
15+
16+
Example 1:
17+
18+
Input: n = 10
19+
Output: 4
20+
Explanation: There are four good numbers in the range [1, 10] : 2, 5, 6, 9.
21+
Note that 1 and 10 are not good numbers, since they remain unchanged after rotating.
22+
Example 2:
23+
24+
Input: n = 1
25+
Output: 0
26+
Example 3:
27+
28+
Input: n = 2
29+
Output: 1
30+
31+
32+
Constraints:
33+
34+
1 <= n <= 104
35+
'''
36+
37+
class Solution:
38+
def digitDP(self,i,tight,changed):
39+
if i==len(self.A):
40+
return int(changed)
41+
ans=0
42+
limit=self.A[i] if tight else 9
43+
for d in range(limit+1):
44+
if d in {3,4,7}:
45+
continue
46+
next_tight=tight and (d==limit)
47+
next_change=changed or d in {2,5,6,9}
48+
ans+=self.digitDP(i+1,next_tight,next_change)
49+
return ans
50+
51+
def rotatedDigits(self, n: int) -> int:
52+
self.A=list(map(int,str(n)))
53+
return self.digitDP(0,True,False)
54+
55+
'''
56+
Complexity Analysis
57+
58+
Time Complexity: O(logN). We do constant-time work for each digit of N.
59+
60+
Space Complexity: O(logN), the space stored by memo.
61+
'''
Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
# Digit DP
2+
3+
Digit DP is dynamic programming over the digits of a number. It is useful when
4+
you need to count or optimize over numbers in a range, especially when the
5+
condition depends on individual digits.
6+
7+
Common examples:
8+
9+
- Count numbers `<= N` with no consecutive equal digits.
10+
- Count numbers `<= N` whose digit sum is `X`.
11+
- Count numbers `<= N` divisible by `K`.
12+
- Count numbers that do not contain digit `4`.
13+
14+
Instead of iterating over every number from `1` to `N`, convert `N` to a string
15+
and build valid numbers one digit at a time.
16+
17+
## Core Idea
18+
19+
For a query like `count valid numbers <= N`:
20+
21+
1. Convert `N` into its digit string `s`.
22+
2. Recursively choose one digit at each position.
23+
3. Use DP to avoid recalculating the same state.
24+
4. Track whether the current prefix is still equal to the prefix of `N`.
25+
26+
The range query `[L, R]` is usually handled as:
27+
28+
```python
29+
answer = solve(R) - solve(L - 1)
30+
```
31+
32+
## Standard State
33+
34+
A typical Digit DP state looks like this:
35+
36+
```python
37+
dp(pos, tight, leading_zero, other_states...)
38+
```
39+
40+
Where:
41+
42+
- `pos`: current index in the digit string.
43+
- `tight`: whether the current prefix is exactly equal to `N`'s prefix.
44+
- `leading_zero`: whether we have only placed leading zeros so far.
45+
- `other_states`: problem-specific information.
46+
47+
Examples of `other_states`:
48+
49+
- `digit_sum`
50+
- `last_digit`
51+
- `remainder`
52+
- `mask` of used digits
53+
- `changed` flag
54+
- `count` of some digit
55+
56+
## Tight Bound
57+
58+
The `tight` flag controls the maximum digit you are allowed to place.
59+
60+
```python
61+
limit = int(s[pos]) if tight else 9
62+
```
63+
64+
For every possible digit:
65+
66+
```python
67+
for d in range(limit + 1):
68+
next_tight = tight and (d == int(s[pos]))
69+
```
70+
71+
If `tight` is `True`, you cannot exceed the current digit of `N`. If you choose
72+
a smaller digit, the remaining positions are free to use `0` through `9`.
73+
74+
## Leading Zeros
75+
76+
Leading zeros let every constructed number have the same length as `N`.
77+
78+
For example, while solving `N = 527`, the number `42` is represented as `042`.
79+
80+
Use `leading_zero` when:
81+
82+
- The number `0` needs special handling.
83+
- A condition should start only after the first non-zero digit.
84+
- You track `last_digit`, digit count, distinct digits, or repeated digits.
85+
86+
Typical update:
87+
88+
```python
89+
next_leading_zero = leading_zero and d == 0
90+
```
91+
92+
If you track `last_digit`, avoid comparing against leading zeros:
93+
94+
```python
95+
if not leading_zero and d == last_digit:
96+
continue
97+
```
98+
99+
## Base Case
100+
101+
When all digits have been processed:
102+
103+
```python
104+
if pos == len(s):
105+
return 1 if valid_state else 0
106+
```
107+
108+
The base case is where you decide whether the number you built should be
109+
counted.
110+
111+
## Example 1: Count Numbers `<= N`
112+
113+
This counts all numbers from `0` to `N`.
114+
115+
```python
116+
from functools import lru_cache
117+
118+
119+
def solve(N):
120+
s = str(N)
121+
122+
@lru_cache(None)
123+
def dp(pos, tight):
124+
if pos == len(s):
125+
return 1
126+
127+
limit = int(s[pos]) if tight else 9
128+
ans = 0
129+
130+
for d in range(limit + 1):
131+
next_tight = tight and (d == int(s[pos]))
132+
ans += dp(pos + 1, next_tight)
133+
134+
return ans
135+
136+
return dp(0, True)
137+
```
138+
139+
## Example 2: Count Numbers With Digit Sum `X`
140+
141+
```python
142+
from functools import lru_cache
143+
144+
145+
def count_numbers(N, target_sum):
146+
s = str(N)
147+
148+
@lru_cache(None)
149+
def dp(pos, tight, digit_sum):
150+
if digit_sum > target_sum:
151+
return 0
152+
153+
if pos == len(s):
154+
return int(digit_sum == target_sum)
155+
156+
limit = int(s[pos]) if tight else 9
157+
ans = 0
158+
159+
for d in range(limit + 1):
160+
next_tight = tight and (d == int(s[pos]))
161+
ans += dp(pos + 1, next_tight, digit_sum + d)
162+
163+
return ans
164+
165+
return dp(0, True, 0)
166+
```
167+
168+
## Example 3: No Consecutive Equal Digits
169+
170+
This version counts numbers in `[1, N]`.
171+
172+
```python
173+
from functools import lru_cache
174+
175+
176+
def count_no_equal_adjacent(N):
177+
if N <= 0:
178+
return 0
179+
180+
s = str(N)
181+
182+
@lru_cache(None)
183+
def dp(pos, tight, leading_zero, last_digit):
184+
if pos == len(s):
185+
return int(not leading_zero)
186+
187+
limit = int(s[pos]) if tight else 9
188+
ans = 0
189+
190+
for d in range(limit + 1):
191+
next_tight = tight and (d == int(s[pos]))
192+
next_leading_zero = leading_zero and d == 0
193+
194+
if not next_leading_zero and not leading_zero and d == last_digit:
195+
continue
196+
197+
next_last_digit = -1 if next_leading_zero else d
198+
ans += dp(pos + 1, next_tight, next_leading_zero, next_last_digit)
199+
200+
return ans
201+
202+
return dp(0, True, True, -1)
203+
```
204+
205+
## Reusable Template
206+
207+
```python
208+
from functools import lru_cache
209+
210+
211+
def solve(N):
212+
if N < 0:
213+
return 0
214+
215+
s = str(N)
216+
217+
@lru_cache(None)
218+
def dp(pos, tight, leading_zero, state):
219+
if pos == len(s):
220+
return int(is_valid(leading_zero, state))
221+
222+
limit = int(s[pos]) if tight else 9
223+
ans = 0
224+
225+
for d in range(limit + 1):
226+
next_tight = tight and (d == int(s[pos]))
227+
next_leading_zero = leading_zero and d == 0
228+
next_state = update(state, d, next_leading_zero)
229+
230+
ans += dp(pos + 1, next_tight, next_leading_zero, next_state)
231+
232+
return ans
233+
234+
return dp(0, True, True, initial_state)
235+
```
236+
237+
## Practice Progression
238+
239+
Beginner:
240+
241+
- Count numbers without digit `4`.
242+
- Count numbers with even digit sum.
243+
- Count numbers containing at least one `7`.
244+
245+
Medium:
246+
247+
- Count numbers with no consecutive equal digits.
248+
- Count numbers divisible by `K`.
249+
- Count numbers with exactly `X` non-zero digits.
250+
251+
Hard:
252+
253+
- Count numbers where digit sum is `K` and the number is divisible by `M`.
254+
- Count numbers with at most `X` distinct digits.
255+
- Count numbers using a digit bitmask and a modulo constraint.
256+
257+
## Common Mistakes
258+
259+
- Forgetting to update `tight` correctly.
260+
- Treating leading zeros as real digits.
261+
- Returning the wrong value in the base case.
262+
- Forgetting to memoize.
263+
- Counting `0` when the problem asks for `[1, N]`.
264+
- Using a mutable object as a DP state.
265+
266+
## Mental Model
267+
268+
Think:
269+
270+
> I am constructing a number digit by digit while staying under the bound `N`.
271+
272+
Every Digit DP problem is mostly about choosing the right state.

0 commit comments

Comments
 (0)