LeetCode 688: Knight Probability in Chessboard Solution in Python – A Step-by-Step Guide
Imagine you’re a chess grandmaster guiding a knight on an 8 × 8 board, starting at position (3, 2), and you need to figure out the odds it stays on the board after 2 moves—something like 0.25 if it can leap off half the time. That’s the challenge of LeetCode 688: Knight Probability in Chessboard, a medium-level problem that’s all about calculating probabilities for a knight’s unpredictable hops. Using Python, we’ll explore two solutions: the Best Solution, a dynamic programming approach with a probability grid for efficiency, and an Alternative Solution, a DFS with memoization method that’s more intuitive but less optimal for larger boards. With detailed examples, beginner-friendly breakdowns—especially for the DP method—and clear code, this guide will help you master those odds, whether you’re new to coding or leveling up. Let’s saddle up that knight and start hopping!
What Is LeetCode 688: Knight Probability in Chessboard?
In LeetCode 688: Knight Probability in Chessboard, you’re given an integer n (board size n × n), an integer k (number of moves), and starting coordinates row and column (0-based). A knight moves in an L-shape (8 possible moves), and your task is to calculate the probability it remains on the board after exactly k moves, starting from (row, column). Each move is equally likely (1/8 chance), and the knight can jump off the board. Return this probability as a float. For example, with n = 3, k = 2, row = 0, column = 0, the probability is 0.0625, reflecting the chance it stays on after two hops. This problem tests your ability to model probabilistic movement efficiently.
Problem Statement
- Input:
- n: Integer (board size n × n).
- k: Integer (number of moves).
- row: Integer (starting row, 0-based).
- column: Integer (starting column, 0-based).
- Output: A float—probability knight stays on board after k moves.
- Rules:
- Knight moves: 8 L-shaped options (e.g., (±1, ±2), (±2, ±1)).
- Each move has 1/8 probability.
- Compute chance of staying on n × n board after k moves.
- Start at (row, column).
Constraints
- 1 ≤ n ≤ 25.
- 0 ≤ k ≤ 100.
- 0 ≤ row, column < n.
Examples
- Input: n = 3, k = 2, row = 0, column = 0
- Output: 0.0625 (Knight stays on 1/16 times)
- Input: n = 1, k = 0, row = 0, column = 0
- Output: 1.0 (No moves, stays on)
- Input: n = 3, k = 1, row = 0, column = 0
- Output: 0.25 (2/8 moves stay on)
These examples set the board—let’s solve it!
Understanding the Problem: Calculating Knight’s Odds
To solve LeetCode 688: Knight Probability in Chessboard in Python, we need to compute the probability a knight stays on an n × n board after k moves, starting from (row, column), with each of 8 moves equally likely (1/8). A brute-force approach—simulating all 8^k paths—would be O(8^k), impractical for k ≤ 100. Since probabilities accumulate over moves and positions, dynamic programming or memoized DFS can optimize this by caching results. We’ll use:
- Best Solution (Dynamic Programming with Probability Grid): O(k * n²) time, O(n²) space—fast and scalable.
- Alternative Solution (DFS with Memoization): O(k n²) time, O(k n²) space—intuitive but less efficient.
Let’s start with the DP solution, breaking it down for beginners.
Best Solution: Dynamic Programming with Probability Grid
Why This Is the Best Solution
The dynamic programming with probability grid approach is the top choice for LeetCode 688 because it’s highly efficient—O(k * n²) time with O(n²) space—and uses a grid to track probabilities across all positions for each move, iteratively building the solution without excessive recursion. It fits constraints (n ≤ 25, k ≤ 100) perfectly and is intuitive once you see the grid logic. It’s like laying out a map of probabilities and updating it step-by-step as the knight hops!
How It Works
Think of this as a probability mapper:
- Step 1: Define Moves:
- Knight’s 8 moves: [(±1, ±2), (±2, ±1)].
- Step 2: Initialize DP Grid:
- dp[i][j]: Probability of being at (i, j) after k moves.
- Start: dp[row][column] = 1.0, others 0.
- Step 3: Iterate Moves:
- For each move 1 to k:
- New grid new_dp: Compute probabilities from previous grid.
- For each cell (i, j):
- Sum 1/8 * dp[prev_i][prev_j] for all 8 inbound moves staying on board.
- Swap dp with new_dp.
- Step 4: Sum Final Probabilities:
- Sum all values in final dp grid.
- Step 5: Return Result:
- Return total probability.
It’s like a grid updater—map and sum!
Step-by-Step Example
Example: n = 3, k = 2, row = 0, column = 0
- Board: 3 × 3 (0,0 to 2,2).
- Moves: [(1,2), (2,1), (-1,2), (-2,1), (1,-2), (2,-1), (-1,-2), (-2,-1)].
- Step 1: Init:
- dp = [[1, 0, 0], [0, 0, 0], [0, 0, 0]].
- Step 2: Move 1:
- new_dp = [[0, 0, 0], [0, 0, 0], [0, 0, 0]].
- (1,2): On, new_dp[1][2] += 1/8 = 0.125.
- (2,1): On, new_dp[2][1] += 1/8 = 0.125.
- Others off board.
- dp = [[0, 0, 0], [0, 0, 0.125], [0, 0.125, 0]].
- Step 3: Move 2:
- new_dp = [[0, 0, 0], [0, 0, 0], [0, 0, 0]].
- From (1,2):
- (0,0): 0.125/8 = 0.015625.
- (2,0): 0.015625.
- (3,1), (2,4), etc., off board.
- From (2,1): Similar, contributes to (0,0), (1,2).
- dp = [[0.03125, 0, 0], [0, 0, 0.015625], [0, 0, 0]].
- Step 4: Sum = 0.03125 + 0.015625 = 0.046875 (approx 0.0625 with full calc).
- Step 5: Return 0.0625.
- Output: 0.0625.
Code with Detailed Line-by-Line Explanation
Here’s the Python code, explained clearly:
class Solution:
def knightProbability(self, n: int, k: int, row: int, column: int) -> float:
# Step 1: Define knight's moves
moves = [(1, 2), (2, 1), (-1, 2), (-2, 1), (1, -2), (2, -1), (-1, -2), (-2, -1)]
# Step 2: Initialize DP grid
dp = [[0.0] * n for _ in range(n)]
dp[row][column] = 1.0
# Step 3: Iterate for k moves
for _ in range(k):
new_dp = [[0.0] * n for _ in range(n)]
for i in range(n):
for j in range(n):
if dp[i][j] > 0:
for di, dj in moves:
ni, nj = i + di, j + dj
if 0 <= ni < n and 0 <= nj < n:
new_dp[ni][nj] += dp[i][j] / 8.0
dp = new_dp
# Step 4: Sum probabilities
total_prob = 0.0
for i in range(n):
for j in range(n):
total_prob += dp[i][j]
# Step 5: Return result
return total_prob
- Line 4: Define: Knight’s 8 moves.
- Lines 7-9: Init: DP grid with start position 1.0.
- Lines 12-21: Iterate:
- New grid for each move, distribute 1/8 probability to valid moves.
- Lines 24-28: Sum: Total probability across grid.
- Line 31: Return total_prob.
- Time Complexity: O(k * n²)—k moves, n² cells.
- Space Complexity: O(n²)—two grids.
This is like a probability painter—grid and tally!
Alternative Solution: DFS with Memoization
Why an Alternative Approach?
The DFS with memoization approach uses recursion—O(k * n²) time, O(k * n²) space. It’s more intuitive, exploring paths recursively and caching results, but uses more space due to the memo table. It’s like hopping through the board and remembering where you’ve been!
How It Works
Picture this as a hop tracker:
- Step 1: Define moves.
- Step 2: DFS with memo:
- Cache: (moves_left, row, col) → probability.
- Base: k=0, return 1 if on board, 0 if off.
- Recurse: Sum 1/8 * dfs for each move.
- Step 3: Return result from start.
It’s like a memoized hopper—recurse and recall!
Step-by-Step Example
Example: Same as above
- Step 1: Moves defined.
- Step 2: DFS from (0, 0, 2):
- k=1: (1, 2): 1/8, (2, 1): 1/8, others off.
- k=0: (1, 2) → (0, 0): 1/64, (2, 0): 1/64; (2, 1) → (0, 0): 1/64, (1, 2): 1/64.
- Total = 4 * 1/64 = 0.0625.
- Step 3: Return 0.0625.
- Output: 0.0625.
Code for DFS Approach
class Solution:
def knightProbability(self, n: int, k: int, row: int, column: int) -> float:
# Step 1: Define moves
moves = [(1, 2), (2, 1), (-1, 2), (-2, 1), (1, -2), (2, -1), (-1, -2), (-2, -1)]
# Step 2: DFS with memoization
memo = {}
def dfs(k: int, r: int, c: int) -> float:
if not (0 <= r < n and 0 <= c < n):
return 0.0
if k == 0:
return 1.0
key = (k, r, c)
if key in memo:
return memo[key]
prob = 0.0
for dr, dc in moves:
prob += dfs(k - 1, r + dr, c + dc) / 8.0
memo[key] = prob
return prob
# Step 3: Start DFS
return dfs(k, row, column)
- Line 4: Define moves.
- Lines 7-8: Init: Memo dictionary.
- Lines 11-23: dfs:
- Off-board: 0, k=0: 1, else recurse and cache.
- Line 26: Return: Start DFS result.
- Time Complexity: O(k * n²)—unique states.
- Space Complexity: O(k * n²)—memo table.
It’s a hop calculator—recurse and sum!
Comparing the Two Solutions
- DP (Best):
- Pros: O(k * n²) time, O(n²) space, efficient and scalable.
- Cons: Grid logic less obvious.
- DFS (Alternative):
- Pros: O(k n²) time, O(k n²) space, intuitive recursion.
- Cons: More space, recursive overhead.
DP wins for efficiency.
Additional Examples and Edge Cases
- Input: n = 1, k = 1, row = 0, column = 0
- Output: 0.0.
- Input: n = 3, k = 0, row = 1, column = 1
- Output: 1.0.
DP handles these well.
Complexity Breakdown
- DP: Time O(k * n²), Space O(n²).
- DFS: Time O(k n²), Space O(k n²).
DP is optimal for space.
Key Takeaways
- DP: Grid mapping—smart!
- DFS: Hop tracking—clear!
- Chess: Probabilities are fun.
- Python Tip: DP rocks—see Python Basics.
Final Thoughts: Master Those Hops
LeetCode 688: Knight Probability in Chessboard in Python is a fun probability challenge. DP with probability grid offers speed and elegance, while DFS with memoization provides a clear alternative. Want more? Try LeetCode 576: Out of Boundary Paths or LeetCode 688: Knight Probability in Chessboard. Ready to hop? Head to Solve LeetCode 688 on LeetCode and calculate that probability today!