351. Android Unlock Patterns 🔒
Problem Description
This problem is about counting valid unlock patterns on an Android lock screen. The lock screen has a 3×3 grid of dots numbered 1-9 (arranged like a phone keypad):
1 2 3 4 5 6 7 8 9
An unlock pattern is a sequence of connected dots where:
- All dots must be distinct - each dot can only be used once in a pattern
- No jumping through unvisited dots - if a line between two consecutive dots passes through the center of another dot, that middle dot must have been visited earlier in the sequence
For example:
- Connecting dots
1→3
requires dot2
to be visited first (since the line passes through dot 2's center) - Connecting dots
1→9
requires dot5
to be visited first (diagonal through center) - Connecting dots
2→9
is allowed without visiting dot5
first (the line doesn't pass through dot 5's center)
The key pairs that require a middle dot to be visited first are:
- Horizontal:
1↔3
(through 2),4↔6
(through 5),7↔9
(through 8) - Vertical:
1↔7
(through 4),2↔8
(through 5),3↔9
(through 6) - Diagonal:
1↔9
(through 5),3↔7
(through 5)
Given two integers m
and n
, you need to find the total number of unique valid unlock patterns that have at least m
dots and at most n
dots.
The solution uses DFS (depth-first search) to explore all possible valid patterns. It maintains a cross
table to track which middle dot must be visited when connecting two dots, and uses symmetry to optimize the calculation (patterns starting from corners are 4-fold symmetric, patterns starting from edges are 4-fold symmetric, and the center is unique).
Flowchart Walkthrough
First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:
Is it a graph?
- No: While the 3×3 grid could be viewed as a graph, the problem is fundamentally about generating valid sequences/patterns, not about graph traversal or graph properties.
Need to solve for kth smallest/largest?
- No: We're counting all valid patterns, not finding a specific kth element.
Involves Linked Lists?
- No: The problem doesn't involve linked list data structures.
Does the problem have small constraints?
- Yes: The grid is fixed at 3×3 (only 9 dots), and patterns have length between
m
andn
where both are at most 9. This is a very small search space.
Brute force / Backtracking?
- Yes: Given the small constraints and the need to explore all possible valid sequences while respecting complex validity rules (no jumping through unvisited dots), backtracking is the natural approach. We need to:
- Try each possible next dot
- Check if it's valid (not visited and no illegal jumps)
- Recursively explore from that dot
- Backtrack by unmarking the dot when done
Conclusion: The flowchart correctly leads us to use a Backtracking approach. The solution implements this by:
- Using DFS to explore all possible patterns starting from each dot
- Maintaining a
vis
array to track visited dots (and backtracking by unmarking them) - Using a
cross
table to check if moves require an intermediate dot to be visited first - Leveraging symmetry (corner dots, edge dots, center dot) to optimize the counting
Intuition
When we think about this problem, we're essentially trying to count all possible ways to draw patterns on the lock screen. Since we need to explore all valid sequences and each choice depends on what we've chosen before (visited dots affect future valid moves), this naturally points to a backtracking approach.
The key insight is recognizing that certain moves are restricted. When connecting two dots, if the straight line passes through another dot's center, that dot must be visited first. For instance, going from dot 1 to dot 3 requires dot 2 to be visited because the line passes directly through it. This creates dependencies between moves.
To handle these dependencies efficiently, we can precompute which dot (if any) must be visited when moving between any two dots. This leads to creating a cross
table where cross[i][j]
tells us which dot must be visited before we can move from dot i
to dot j
. Most moves don't have restrictions (like 1→2 or 2→9), so most entries in this table are 0.
The next crucial observation is about symmetry. The 3×3 grid has rotational and reflective symmetry:
- Corner dots (1, 3, 7, 9) are all equivalent - patterns starting from any corner can be rotated to match patterns from other corners
- Edge dots (2, 4, 6, 8) are similarly equivalent
- The center dot (5) is unique
This means instead of starting our search from all 9 positions, we only need to compute patterns starting from:
- One corner (multiply result by 4)
- One edge (multiply result by 4)
- The center (use result as is)
The backtracking algorithm then becomes straightforward: from each starting position, we try to extend the pattern to every unvisited dot that we can legally reach (either directly or when the required intermediate dot has been visited). We count valid patterns when their length falls within the [m, n]
range, and backtrack by unmarking dots after exploring all possibilities from them.
Solution Approach
The solution implements a depth-first search with backtracking to explore all valid unlock patterns. Let's walk through the implementation step by step:
1. Setting up the Cross Table
First, we create a 10×10 table (using indices 1-9, ignoring index 0) to store which dot must be visited when moving between two dots:
cross = [[0] * 10 for _ in range(10)]
cross[1][3] = cross[3][1] = 2 # Moving between 1 and 3 requires 2
cross[1][7] = cross[7][1] = 4 # Moving between 1 and 7 requires 4
cross[1][9] = cross[9][1] = 5 # Moving between 1 and 9 requires 5
# ... and so on for all restricted moves
The table is symmetric since the restriction works both ways. Most entries remain 0, meaning no intermediate dot is required.
2. DFS with Backtracking
The core algorithm is implemented in the dfs
function:
def dfs(i: int, cnt: int = 1) -> int:
if cnt > n:
return 0
vis[i] = True
ans = int(cnt >= m)
for j in range(1, 10):
x = cross[i][j]
if not vis[j] and (x == 0 or vis[x]):
ans += dfs(j, cnt + 1)
vis[i] = False
return ans
The function works as follows:
i
is the current dot position,cnt
is the current pattern length- If pattern length exceeds
n
, return 0 (invalid pattern) - Mark current dot as visited:
vis[i] = True
- If current length is within
[m, n]
, count this as a valid pattern:ans = int(cnt >= m)
- Try extending to each unvisited dot
j
:- Check if move is valid: either no intermediate dot required (
x == 0
) or the intermediate dot is already visited (vis[x]
) - Recursively explore from dot
j
with incremented count
- Check if move is valid: either no intermediate dot required (
- Backtrack by unmarking the current dot:
vis[i] = False
- Return total count of valid patterns found
3. Exploiting Symmetry
Instead of starting DFS from all 9 positions, we leverage symmetry:
return dfs(1) * 4 + dfs(2) * 4 + dfs(5)
dfs(1) * 4
: Patterns starting from corner (1, 3, 7, 9 are equivalent)dfs(2) * 4
: Patterns starting from edge (2, 4, 6, 8 are equivalent)dfs(5)
: Patterns starting from center (unique position)
4. Visited Array
The vis
array tracks which dots have been used in the current pattern:
vis = [False] * 10
This array is crucial for:
- Preventing revisiting dots (ensuring all dots are distinct)
- Checking if required intermediate dots have been visited
- Backtracking by resetting the visited status
The time complexity is O(9!) in the worst case (exploring all permutations), but practically much less due to early termination and constraints. The space complexity is O(1) for the fixed-size data structures plus O(n) for the recursion stack depth.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through finding all valid patterns of length 2-3 (m=2, n=3) to illustrate the solution approach.
Setup:
- Create a
cross
table wherecross[i][j]
stores the dot that must be visited before moving from i to j - Initialize
vis
array to track visited dots
Starting from Corner (dot 1):
-
Mark dot 1 as visited:
vis[1] = True
-
Since length is 1 (< m=2), don't count this as valid yet
-
Try extending to each unvisited dot:
- 1→2: No restriction (
cross[1][2] = 0
), valid move- Pattern [1,2]: length=2, count as valid (✓)
- Try extending: 2→3, 2→4, 2→5, 2→6, 2→7, 2→8, 2→9 all valid
- Each creates a length-3 pattern like [1,2,3], [1,2,4], etc. (✓)
- 1→3: Requires dot 2 (
cross[1][3] = 2
), but dot 2 not visited, skip - 1→4: No restriction, valid move
- Pattern [1,4]: length=2 (✓)
- Extend to get patterns like [1,4,2], [1,4,5], [1,4,7], etc.
- 1→5: No restriction, valid move
- Pattern [1,5]: length=2 (✓)
- Can now reach 3,7,9 through center: [1,5,3], [1,5,7], [1,5,9]
- 1→7: Requires dot 4 (
cross[1][7] = 4
), dot 4 not visited, skip - 1→9: Requires dot 5 (
cross[1][9] = 5
), dot 5 not visited, skip
- 1→2: No restriction (
-
Backtrack:
vis[1] = False
Key observations from this partial exploration:
- Direct neighbors (like 1→2, 1→4) are always valid
- Restricted moves (1→3, 1→7, 1→9) become available only after visiting the middle dot
- Once we visit dot 5, diagonal moves like 1→9 become possible in subsequent steps
Starting from Edge (dot 2): Similar process, but dot 2 has different connectivity:
- Can reach 1,3,4,5,6,7,8,9 directly (no restrictions from dot 2)
- More flexibility in creating patterns
Starting from Center (dot 5):
- Can reach all 8 surrounding dots directly
- Maximum flexibility, no restricted moves from center
Final Calculation:
Total = dfs(1) × 4 + dfs(2) × 4 + dfs(5)
For our example with m=2, n=3:
- Patterns from corner (×4 for symmetry)
- Patterns from edge (×4 for symmetry)
- Patterns from center (×1, unique)
This symmetry optimization reduces computation from 9 starting points to just 3, making the algorithm much more efficient while still counting all valid patterns correctly.
Solution Implementation
1class Solution:
2 def numberOfPatterns(self, m: int, n: int) -> int:
3 def count_patterns_from_position(current_pos: int, pattern_length: int = 1) -> int:
4 """
5 Count valid patterns starting from current_pos using DFS.
6
7 Args:
8 current_pos: Current position on the 3x3 grid (1-9)
9 pattern_length: Current length of the pattern being formed
10
11 Returns:
12 Number of valid patterns from this state
13 """
14 # Base case: pattern too long
15 if pattern_length > n:
16 return 0
17
18 # Mark current position as visited
19 visited[current_pos] = True
20
21 # Count this pattern if it meets minimum length requirement
22 valid_patterns = 1 if pattern_length >= m else 0
23
24 # Try extending pattern to each unvisited position
25 for next_pos in range(1, 10):
26 middle_point = skip_map[current_pos][next_pos]
27
28 # Can move to next_pos if:
29 # 1. It's not visited yet
30 # 2. Either no middle point exists (adjacent/diagonal move)
31 # or the middle point is already visited
32 if not visited[next_pos] and (middle_point == 0 or visited[middle_point]):
33 valid_patterns += count_patterns_from_position(next_pos, pattern_length + 1)
34
35 # Backtrack: unmark current position
36 visited[current_pos] = False
37
38 return valid_patterns
39
40 # Initialize skip_map: stores which number must be visited when jumping between non-adjacent positions
41 # skip_map[i][j] = k means moving from i to j requires k to be visited first
42 skip_map = [[0] * 10 for _ in range(10)]
43
44 # Horizontal skips (crossing middle column)
45 skip_map[1][3] = skip_map[3][1] = 2
46 skip_map[4][6] = skip_map[6][4] = 5
47 skip_map[7][9] = skip_map[9][7] = 8
48
49 # Vertical skips (crossing middle row)
50 skip_map[1][7] = skip_map[7][1] = 4
51 skip_map[2][8] = skip_map[8][2] = 5
52 skip_map[3][9] = skip_map[9][3] = 6
53
54 # Diagonal skips (crossing center)
55 skip_map[1][9] = skip_map[9][1] = 5
56 skip_map[3][7] = skip_map[7][3] = 5
57
58 # Track visited positions
59 visited = [False] * 10
60
61 # Calculate total patterns:
62 # - Corner positions (1,3,7,9) are symmetric: multiply by 4
63 # - Edge positions (2,4,6,8) are symmetric: multiply by 4
64 # - Center position (5) is unique: multiply by 1
65 return (count_patterns_from_position(1) * 4 +
66 count_patterns_from_position(2) * 4 +
67 count_patterns_from_position(5))
68
1class Solution {
2 private int minPatternLength;
3 private int maxPatternLength;
4 private int[][] crossingPoint = new int[10][10]; // Stores which key must be visited to cross between two keys
5 private boolean[] visited = new boolean[10]; // Tracks which keys have been used in current pattern
6
7 public int numberOfPatterns(int m, int n) {
8 this.minPatternLength = m;
9 this.maxPatternLength = n;
10
11 // Initialize the crossing points between non-adjacent keys
12 // For example, to go from key 1 to key 3, you must pass through key 2
13 crossingPoint[1][3] = crossingPoint[3][1] = 2;
14 crossingPoint[1][7] = crossingPoint[7][1] = 4;
15 crossingPoint[1][9] = crossingPoint[9][1] = 5;
16 crossingPoint[2][8] = crossingPoint[8][2] = 5;
17 crossingPoint[3][7] = crossingPoint[7][3] = 5;
18 crossingPoint[3][9] = crossingPoint[9][3] = 6;
19 crossingPoint[4][6] = crossingPoint[6][4] = 5;
20 crossingPoint[7][9] = crossingPoint[9][7] = 8;
21
22 // Calculate total patterns by leveraging symmetry:
23 // - Corner keys (1, 3, 7, 9) have the same number of patterns (multiply by 4)
24 // - Edge keys (2, 4, 6, 8) have the same number of patterns (multiply by 4)
25 // - Center key (5) is unique (multiply by 1)
26 return dfs(1, 1) * 4 + dfs(2, 1) * 4 + dfs(5, 1);
27 }
28
29 private int dfs(int currentKey, int patternLength) {
30 // If pattern length exceeds maximum, stop exploring
31 if (patternLength > maxPatternLength) {
32 return 0;
33 }
34
35 // Mark current key as visited
36 visited[currentKey] = true;
37
38 // If current pattern length is within valid range, count it as valid
39 int validPatterns = patternLength >= minPatternLength ? 1 : 0;
40
41 // Try to extend the pattern to all other keys
42 for (int nextKey = 1; nextKey < 10; ++nextKey) {
43 int requiredCrossingKey = crossingPoint[currentKey][nextKey];
44
45 // Can move to nextKey if:
46 // 1. nextKey hasn't been visited yet, AND
47 // 2. Either there's no crossing point (adjacent keys) OR the crossing point has been visited
48 if (!visited[nextKey] && (requiredCrossingKey == 0 || visited[requiredCrossingKey])) {
49 validPatterns += dfs(nextKey, patternLength + 1);
50 }
51 }
52
53 // Backtrack: unmark current key for other paths
54 visited[currentKey] = false;
55
56 return validPatterns;
57 }
58}
59
1class Solution {
2public:
3 int numberOfPatterns(int m, int n) {
4 // Initialize array to store intermediate points between non-adjacent keys
5 // cross[i][j] represents the key that must be visited when moving from key i to key j
6 int cross[10][10];
7 memset(cross, 0, sizeof(cross));
8
9 // Track visited keys in the current pattern
10 bool visited[10];
11 memset(visited, false, sizeof(visited));
12
13 // Define all pairs of keys that require crossing over another key
14 // Horizontal crossings
15 cross[1][3] = cross[3][1] = 2; // 1-3 requires crossing 2
16 cross[4][6] = cross[6][4] = 5; // 4-6 requires crossing 5
17 cross[7][9] = cross[9][7] = 8; // 7-9 requires crossing 8
18
19 // Vertical crossings
20 cross[1][7] = cross[7][1] = 4; // 1-7 requires crossing 4
21 cross[2][8] = cross[8][2] = 5; // 2-8 requires crossing 5
22 cross[3][9] = cross[9][3] = 6; // 3-9 requires crossing 6
23
24 // Diagonal crossings
25 cross[1][9] = cross[9][1] = 5; // 1-9 requires crossing 5
26 cross[3][7] = cross[7][3] = 5; // 3-7 requires crossing 5
27
28 // Define DFS function to count valid patterns
29 function<int(int, int)> dfs = [&](int currentKey, int patternLength) {
30 // Prune if pattern length exceeds maximum
31 if (patternLength > n) {
32 return 0;
33 }
34
35 // Mark current key as visited
36 visited[currentKey] = true;
37
38 // Count current pattern if it meets minimum length requirement
39 int totalPatterns = (patternLength >= m) ? 1 : 0;
40
41 // Try extending pattern to each unvisited key
42 for (int nextKey = 1; nextKey <= 9; ++nextKey) {
43 int intermediateKey = cross[currentKey][nextKey];
44
45 // Check if move is valid:
46 // 1. Next key must not be visited
47 // 2. Either no intermediate key exists (adjacent move) or intermediate key is already visited
48 if (!visited[nextKey] && (intermediateKey == 0 || visited[intermediateKey])) {
49 totalPatterns += dfs(nextKey, patternLength + 1);
50 }
51 }
52
53 // Backtrack: unmark current key for other pattern explorations
54 visited[currentKey] = false;
55
56 return totalPatterns;
57 };
58
59 // Calculate total patterns by symmetry:
60 // - Corner keys (1,3,7,9): multiply by 4 due to symmetry
61 // - Edge keys (2,4,6,8): multiply by 4 due to symmetry
62 // - Center key (5): unique position, count once
63 return dfs(1, 1) * 4 + dfs(2, 1) * 4 + dfs(5, 1);
64 }
65};
66
1/**
2 * Counts the number of valid unlock patterns for Android lock screen
3 * @param m - Minimum pattern length
4 * @param n - Maximum pattern length
5 * @returns Number of valid patterns
6 */
7function numberOfPatterns(m: number, n: number): number {
8 // Matrix to store which number must be crossed when moving between two keys
9 // crossMatrix[i][j] stores the key that must be visited when moving from i to j
10 const crossMatrix: number[][] = Array(10)
11 .fill(0)
12 .map(() => Array(10).fill(0));
13
14 // Track which keys have been visited in current pattern
15 const visited: boolean[] = Array(10).fill(false);
16
17 // Initialize crossing requirements for diagonal and straight line moves
18 // Corners to corners (diagonals)
19 crossMatrix[1][3] = crossMatrix[3][1] = 2; // 1↔3 must cross 2
20 crossMatrix[1][7] = crossMatrix[7][1] = 4; // 1↔7 must cross 4
21 crossMatrix[1][9] = crossMatrix[9][1] = 5; // 1↔9 must cross 5
22 crossMatrix[3][7] = crossMatrix[7][3] = 5; // 3↔7 must cross 5
23 crossMatrix[3][9] = crossMatrix[9][3] = 6; // 3↔9 must cross 6
24 crossMatrix[7][9] = crossMatrix[9][7] = 8; // 7↔9 must cross 8
25
26 // Edges to edges (straight lines)
27 crossMatrix[2][8] = crossMatrix[8][2] = 5; // 2↔8 must cross 5
28 crossMatrix[4][6] = crossMatrix[6][4] = 5; // 4↔6 must cross 5
29
30 /**
31 * Depth-first search to explore all valid patterns
32 * @param currentKey - Current key position (1-9)
33 * @param patternLength - Current pattern length
34 * @returns Number of valid patterns starting from currentKey
35 */
36 const dfs = (currentKey: number, patternLength: number): number => {
37 // Prune if pattern exceeds maximum length
38 if (patternLength > n) {
39 return 0;
40 }
41
42 // Mark current key as visited
43 visited[currentKey] = true;
44
45 let patternCount: number = 0;
46
47 // Count current pattern if it meets minimum length requirement
48 if (patternLength >= m) {
49 patternCount++;
50 }
51
52 // Try all possible next keys (1-9)
53 for (let nextKey: number = 1; nextKey < 10; nextKey++) {
54 const crossedKey: number = crossMatrix[currentKey][nextKey];
55
56 // Move is valid if:
57 // 1. Next key hasn't been visited
58 // 2. Either no crossing required (crossedKey === 0) or crossed key was already visited
59 if (!visited[nextKey] && (crossedKey === 0 || visited[crossedKey])) {
60 patternCount += dfs(nextKey, patternLength + 1);
61 }
62 }
63
64 // Backtrack: unmark current key for other paths
65 visited[currentKey] = false;
66
67 return patternCount;
68 };
69
70 // Calculate total patterns by symmetry:
71 // - Corners (1, 3, 7, 9) are symmetric, so calculate once and multiply by 4
72 // - Edges (2, 4, 6, 8) are symmetric, so calculate once and multiply by 4
73 // - Center (5) is unique, calculate once
74 return dfs(1, 1) * 4 + dfs(2, 1) * 4 + dfs(5, 1);
75}
76
Time and Space Complexity
Time Complexity: O(9!)
The algorithm uses DFS to explore all possible pattern combinations. In the worst case (when n = 9
), we need to explore all possible permutations of placing digits 1-9 in sequence.
- From each position, we can potentially visit up to 9 different next positions
- The maximum depth of recursion is
n
- At each level, we check all 9 positions, leading to a branching factor that decreases as more positions are visited
- The upper bound is
9 * 8 * 7 * ... * 1 = 9!
possible paths to explore - The algorithm is called 9 times total (4 times for corner positions, 4 times for edge positions, 1 time for center), but this doesn't change the factorial nature of the complexity
Space Complexity: O(1)
The space complexity consists of:
cross
matrix:10 × 10 = 100
space, which isO(1)
constant spacevis
array: size 10, which isO(1)
constant space- Recursion stack: maximum depth is
n
, which is at most 9, soO(1)
constant space - Local variables in each recursive call:
O(1)
per call
Since all space requirements are bounded by constants independent of input size, the overall space complexity is O(1)
.
Common Pitfalls
1. Incorrectly Handling the Skip Logic
Pitfall: A common mistake is misunderstanding when a middle dot needs to be visited. Developers often assume that ANY move between non-adjacent dots requires visiting an intermediate dot, but this is only true when the line passes directly through another dot's center.
Example of the mistake:
# WRONG: Assuming 2→9 requires visiting 5 first
if abs(current - next) > 1: # This logic is too simplistic
# Check for middle dot...
Why it's wrong: The move 2→9 doesn't require visiting 5 first because the line doesn't pass through dot 5's center. Only specific pairs like 1→3, 1→9, etc., have this restriction.
Solution: Use an explicit mapping table that only includes the pairs that actually require a middle dot:
skip_map = [[0] * 10 for _ in range(10)]
# Only set values for moves that actually cross through another dot's center
skip_map[1][3] = skip_map[3][1] = 2 # Horizontal through middle
skip_map[1][9] = skip_map[9][1] = 5 # Diagonal through center
# ... other specific pairs
2. Forgetting to Backtrack Properly
Pitfall: Failing to reset the visited state after exploring a path, which causes dots to remain marked as visited for subsequent explorations.
Example of the mistake:
def dfs(current, length):
visited[current] = True
result = 0
for next_pos in range(1, 10):
if not visited[next_pos]:
result += dfs(next_pos, length + 1)
# WRONG: Forgot to reset visited[current] = False
return result
Why it's wrong: Without resetting, once a dot is visited in any path, it remains marked as visited for all subsequent paths, drastically reducing the count of valid patterns.
Solution: Always reset the visited state before returning:
def dfs(current, length):
visited[current] = True
result = 0
# ... exploration logic ...
visited[current] = False # Critical backtracking step
return result
3. Incorrect Base Case for Counting Valid Patterns
Pitfall: Only counting patterns when length equals exactly m or n, instead of counting all patterns with length in the range [m, n].
Example of the mistake:
# WRONG: Only counts patterns of exact lengths if pattern_length == m or pattern_length == n: valid_patterns = 1 else: valid_patterns = 0
Why it's wrong: The problem asks for patterns with "at least m dots and at most n dots", meaning any length from m to n inclusive should be counted.
Solution: Count any pattern with length in the valid range:
# Correct: Count if length is anywhere in [m, n] valid_patterns = 1 if pattern_length >= m else 0 # Continue exploring until pattern_length > n
4. Missing Symmetry Optimization or Applying It Incorrectly
Pitfall: Either not using symmetry at all (causing unnecessary computation) or incorrectly grouping symmetric positions.
Example of the mistake:
# WRONG: Treating all positions as unique
total = 0
for start in range(1, 10):
total += dfs(start)
Why it's wrong: This calculates patterns for all 9 starting positions independently, missing the 4-fold symmetry of corners and edges.
Solution: Group symmetric positions correctly:
# Corners (1,3,7,9) are 4-fold symmetric # Edges (2,4,6,8) are 4-fold symmetric # Center (5) is unique return dfs(1) * 4 + dfs(2) * 4 + dfs(5)
5. Off-by-One Errors with Array Indexing
Pitfall: Using 0-based indexing for the dots when the problem naturally uses 1-9 numbering.
Example of the mistake:
# WRONG: Using 0-8 indexing
visited = [False] * 9
for next_pos in range(9): # 0 to 8
if not visited[next_pos]:
# ...
Why it's wrong: This misaligns with the natural 1-9 numbering of the lock screen, making the skip_map logic confusing and error-prone.
Solution: Use 1-based indexing with size 10 arrays:
visited = [False] * 10 # Index 0 unused, indices 1-9 for dots
for next_pos in range(1, 10): # 1 to 9
if not visited[next_pos]:
# ...
Consider the classic dynamic programming of fibonacci numbers, what is the recurrence relation?
Recommended Readings
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
Backtracking Template Prereq DFS with States problems dfs_with_states Combinatorial search problems Combinatorial search problems involve finding groupings and assignments of objects that satisfy certain conditions Finding all permutations combinations subsets and solving Sudoku are classic combinatorial problems The time complexity of combinatorial problems often grows rapidly with the size of
Bitmask and Dynamic Programming Bit manipulation is a crucial aspect of computer programming and one of the most powerful tools for bit manipulation is bitmasks Let's first understand what a bit is A bit is a binary digit It's the smallest piece of data in a computer and can be
Want a Structured Path to Master System Design Too? Don’t Miss This!