2912. Number of Ways to Reach Destination in the Grid 🔒
Problem Description
You have a grid with dimensions n × m
(both 1-indexed), and you need to find the number of ways to move from a starting position source
to an ending position dest
in exactly k
moves.
The movement rules are:
- From any cell
[x₁, y₁]
, you can move to another cell[x₂, y₂]
if either:x₁ == x₂
(same row, different column), ORy₁ == y₂
(same column, different row)
- You cannot stay in the same cell (both coordinates must not be the same)
Given:
n
: number of rows in the gridm
: number of columns in the gridk
: exact number of moves to makesource
: starting position[x, y]
(1-indexed)dest
: destination position[x, y]
(1-indexed)
Return the number of ways to reach dest
from source
in exactly k
moves, modulo 10⁹ + 7
.
For example, in a move from cell [2, 3]
, you could go to:
- Any cell in row 2 except
[2, 3]
itself - Any cell in column 3 except
[2, 3]
itself
The solution uses dynamic programming with state transitions. It tracks four states based on the relationship between current position and source:
- State 0: at the source position itself
- State 1: different row, same column as source
- State 2: same row, different column as source
- State 3: different row and different column from source
The transitions between states are calculated based on how many valid moves are possible from each state, considering the grid dimensions and movement constraints.
Intuition
The key insight is recognizing that after any number of moves, the position relative to the source can only be in one of four states:
- Back at the source itself
- Different row but same column as source
- Same row but different column as source
- Different row AND different column from source
Why does this matter? Because the number of ways to reach any specific cell depends only on which of these four states it belongs to, not on the exact coordinates. This dramatically reduces the problem complexity.
Consider what happens when we make a move:
- From the source position, we can move to
(n-1)
cells in the same column or(m-1)
cells in the same row - From a position in the same column (but different row), we can return to source, move to
(n-2)
other cells in that column, or jump to any of(m-1)
columns (landing in state 3) - Similar logic applies for positions in the same row or in completely different rows/columns
Instead of tracking paths to every possible cell (which would be computationally expensive), we track only how many ways we can reach each of these four states after each move. This transforms an intractable problem into one with just 4 variables that we update k
times.
The transition formulas emerge naturally when we count the valid moves from each state:
g[0]
: Ways to return to source = from state 1 (same column) we have(n-1)
such positions, plus from state 2 (same row) we have(m-1)
such positionsg[1]
: Ways to reach same column = from source (1 way) + from within same column(n-2)
ways + from different row/column positions that can jump to this column(m-1)
ways- And so on...
After k
iterations, we simply check which state the destination belongs to relative to the source and return the corresponding count.
Learn more about Math, Dynamic Programming and Combinatorics patterns.
Solution Approach
We implement the solution using dynamic programming with state transitions. The approach uses four variables to track the number of ways to reach each state after each move.
State Definition:
a
(orf[0]
): number of ways to be at the source positionb
(orf[1]
): number of ways to be in a different row but same column as sourcec
(orf[2]
): number of ways to be in the same row but different column as sourced
(orf[3]
): number of ways to be in a different row AND different column from source
Initialization:
We start with a = 1
(we begin at the source), and b = c = d = 0
(we haven't moved yet).
State Transitions:
For each of the k
moves, we calculate the new state values based on the current state:
aa = (n - 1) × b + (m - 1) × c bb = a + (n - 2) × b + (m - 1) × d cc = a + (m - 2) × c + (n - 1) × d dd = b + c + (n - 2) × d + (m - 2) × d
Breaking down each transition:
aa
: To reach source, we can come from(n-1)
positions in the same column (stateb
) or(m-1)
positions in the same row (statec
)bb
: To reach a different row in the same column, we can come from source (1 way), from(n-2)
other positions in that column, or from(m-1)
different columns in different rowscc
: To reach a different column in the same row, we can come from source (1 way), from(m-2)
other positions in that row, or from(n-1)
different rows in different columnsdd
: To reach a different row and column, we can come from stateb
(1 way), statec
(1 way), or from(n-2) + (m-2)
other positions in stated
Modulo Operation:
All calculations are performed modulo 10^9 + 7
to prevent overflow.
Final Answer:
After k
iterations, we determine which state the destination belongs to:
- If
source[0] == dest[0]
(same row):- If
source[1] == dest[1]
: returna
(destination is source) - Else: return
c
(different column, same row)
- If
- Else (different row):
- If
source[1] == dest[1]
: returnb
(different row, same column) - Else: return
d
(different row and column)
- If
The time complexity is O(k)
and space complexity is O(1)
since we only maintain four variables throughout the computation.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's work through a small example with a 3×3 grid, moving from source [1,1]
to destination [2,3]
in exactly k=2
moves.
Initial Setup:
- Grid: 3×3 (n=3, m=3)
- Source:
[1,1]
- Destination:
[2,3]
(different row AND different column from source) - States:
a=1, b=0, c=0, d=0
(we start at source)
Move 1:
From source [1,1]
, we can move to:
- Same column (column 1):
[2,1]
and[3,1]
→ 2 positions (state b) - Same row (row 1):
[1,2]
and[1,3]
→ 2 positions (state c)
Calculating new states:
aa = (3-1)×0 + (3-1)×0 = 0
(can't return to source in 1 move)bb = 1 + (3-2)×0 + (3-1)×0 = 1
(1 way from source)cc = 1 + (3-2)×0 + (3-1)×0 = 1
(1 way from source)dd = 0 + 0 + (3-2)×0 + (3-2)×0 = 0
(can't reach different row AND column in 1 move)
After move 1: a=0, b=1, c=1, d=0
This means:
- 0 ways to be at
[1,1]
(source) - 1 way to be in column 1 but different row (either
[2,1]
or[3,1]
) - 1 way to be in row 1 but different column (either
[1,2]
or[1,3]
) - 0 ways to be in different row AND column
Move 2: Now we calculate transitions from the current states:
-
aa = (3-1)×1 + (3-1)×1 = 2 + 2 = 4
- From state b (different row, same column): 2 positions can reach source
- From state c (same row, different column): 2 positions can reach source
-
bb = 0 + (3-2)×1 + (3-1)×0 = 0 + 1 + 0 = 1
- From source: 0 ways (not at source)
- From within same column: 1 way (can move within column)
- From state d: 0 ways (no positions in state d)
-
cc = 0 + (3-2)×1 + (3-1)×0 = 0 + 1 + 0 = 1
- From source: 0 ways
- From within same row: 1 way
- From state d: 0 ways
-
dd = 1 + 1 + (3-2)×0 + (3-2)×0 = 2
- From state b: 1 way (can jump to any different column)
- From state c: 1 way (can jump to any different row)
- From state d: 0 ways (no positions there yet)
After move 2: a=4, b=1, c=1, d=2
Final Answer:
Destination [2,3]
is in a different row AND different column from source [1,1]
, so it belongs to state d.
Therefore, there are 2 ways to reach [2,3]
from [1,1]
in exactly 2 moves.
We can verify this by listing the actual paths:
[1,1] → [1,3] → [2,3]
(move along row 1, then along column 3)[1,1] → [2,1] → [2,3]
(move along column 1, then along row 2)
The dynamic programming approach correctly counts these paths without explicitly enumerating them, making it efficient even for large values of k.
Solution Implementation
1class Solution:
2 def numberOfWays(
3 self, n: int, m: int, k: int, source: List[int], dest: List[int]
4 ) -> int:
5 MOD = 10**9 + 7
6
7 # Dynamic programming states after i steps:
8 # same_pos: number of ways to stay at the same position
9 # same_row_diff_col: number of ways to be in same row but different column
10 # diff_row_same_col: number of ways to be in different row but same column
11 # diff_row_diff_col: number of ways to be in different row and different column
12
13 # Initialize for 0 steps (starting position)
14 same_pos = 1
15 same_row_diff_col = 0
16 diff_row_same_col = 0
17 diff_row_diff_col = 0
18
19 # Calculate transitions for each step
20 for _ in range(k):
21 # Calculate next state values based on current state
22
23 # To stay at same position: can come from same row (n-1 choices) or same column (m-1 choices)
24 next_same_pos = ((n - 1) * same_row_diff_col + (m - 1) * diff_row_same_col) % MOD
25
26 # To reach same row, different column:
27 # - from same position (1 way to any of m-1 columns)
28 # - from same row different column (n-2 other columns to current)
29 # - from different row different column (m-1 columns in same row)
30 next_same_row_diff_col = (same_pos + (n - 2) * same_row_diff_col + (m - 1) * diff_row_diff_col) % MOD
31
32 # To reach different row, same column:
33 # - from same position (1 way to any of n-1 rows)
34 # - from different row same column (m-2 other rows to current)
35 # - from different row different column (n-1 rows in same column)
36 next_diff_row_same_col = (same_pos + (m - 2) * diff_row_same_col + (n - 1) * diff_row_diff_col) % MOD
37
38 # To reach different row, different column:
39 # - from same row different column (1 way to any of n-1 rows)
40 # - from different row same column (1 way to any of m-1 columns)
41 # - from different row different column ((n-2) + (m-2) ways to stay in different state)
42 next_diff_row_diff_col = (same_row_diff_col + diff_row_same_col + (n - 2) * diff_row_diff_col + (m - 2) * diff_row_diff_col) % MOD
43
44 # Update states for next iteration
45 same_pos = next_same_pos
46 same_row_diff_col = next_same_row_diff_col
47 diff_row_same_col = next_diff_row_same_col
48 diff_row_diff_col = next_diff_row_diff_col
49
50 # Return the appropriate count based on source and destination relationship
51 if source[0] == dest[0]: # Same row
52 if source[1] == dest[1]: # Same position
53 return same_pos
54 else: # Different column
55 return diff_row_same_col
56 else: # Different row
57 if source[1] == dest[1]: # Same column
58 return same_row_diff_col
59 else: # Different column
60 return diff_row_diff_col
61
1class Solution {
2 public int numberOfWays(int n, int m, int k, int[] source, int[] dest) {
3 final int MOD = 1000000007;
4
5 // Dynamic programming array to track number of ways for each state
6 // State 0: at source position (same row and column as source)
7 // State 1: same row as source, different column
8 // State 2: different row, same column as source
9 // State 3: different row and different column from source
10 long[] dp = new long[4];
11 dp[0] = 1; // Initially at source position
12
13 // Perform k transitions
14 while (k-- > 0) {
15 long[] nextDp = new long[4];
16
17 // Transition to state 0 (at source position)
18 // Can come from state 1 (move within same row) or state 2 (move within same column)
19 nextDp[0] = ((n - 1) * dp[1] + (m - 1) * dp[2]) % MOD;
20
21 // Transition to state 1 (same row as source, different column)
22 // Can come from state 0 (move to different column in source row)
23 // or state 1 (move within same row but not to source column)
24 // or state 3 (move from different row to source row)
25 nextDp[1] = (dp[0] + (n - 2) * dp[1] + (m - 1) * dp[3]) % MOD;
26
27 // Transition to state 2 (different row, same column as source)
28 // Can come from state 0 (move to different row in source column)
29 // or state 2 (move within same column but not to source row)
30 // or state 3 (move from different column to source column)
31 nextDp[2] = (dp[0] + (m - 2) * dp[2] + (n - 1) * dp[3]) % MOD;
32
33 // Transition to state 3 (different row and column from source)
34 // Can come from state 1 (move to different row)
35 // or state 2 (move to different column)
36 // or state 3 (move to another position that's still different from source)
37 nextDp[3] = (dp[1] + dp[2] + (n - 2) * dp[3] + (m - 2) * dp[3]) % MOD;
38
39 dp = nextDp;
40 }
41
42 // Determine which state corresponds to the destination position
43 if (source[0] == dest[0]) {
44 // Same row as source
45 return source[1] == dest[1] ? (int) dp[0] : (int) dp[2];
46 }
47 // Different row from source
48 return source[1] == dest[1] ? (int) dp[1] : (int) dp[3];
49 }
50}
51
1class Solution {
2public:
3 int numberOfWays(int n, int m, int k, vector<int>& source, vector<int>& dest) {
4 const int MOD = 1e9 + 7;
5
6 // Dynamic programming states:
7 // dp[0]: at source position (same row, same column)
8 // dp[1]: same row as source, different column
9 // dp[2]: different row from source, same column
10 // dp[3]: different row and different column from source
11 vector<long long> dp(4);
12 dp[0] = 1; // Initially at source position
13
14 // Perform k transitions
15 while (k--) {
16 vector<long long> nextDp(4);
17
18 // Transitions to state 0 (back to source position):
19 // - From state 1: choose any of (n-1) other rows to move to source column
20 // - From state 2: choose any of (m-1) other columns to move to source row
21 nextDp[0] = ((n - 1) * dp[1] + (m - 1) * dp[2]) % MOD;
22
23 // Transitions to state 1 (same row, different column):
24 // - From state 0: move to any of (n-1) other rows
25 // - From state 1: stay in same row, move to any of (n-2) other rows (excluding source row)
26 // - From state 3: move to source row from any of (m-1) columns
27 nextDp[1] = (dp[0] + (n - 2) * dp[1] + (m - 1) * dp[3]) % MOD;
28
29 // Transitions to state 2 (different row, same column):
30 // - From state 0: move to any of (m-1) other columns
31 // - From state 2: stay in same column, move to any of (m-2) other columns (excluding source column)
32 // - From state 3: move to source column from any of (n-1) rows
33 nextDp[2] = (dp[0] + (m - 2) * dp[2] + (n - 1) * dp[3]) % MOD;
34
35 // Transitions to state 3 (different row, different column):
36 // - From state 1: move to any of (m-2) other columns (not source column)
37 // - From state 2: move to any of (n-2) other rows (not source row)
38 // - From state 3: stay in different row/column, move to (n-2) other rows or (m-2) other columns
39 nextDp[3] = (dp[1] + dp[2] + (n - 2) * dp[3] + (m - 2) * dp[3]) % MOD;
40
41 dp = move(nextDp);
42 }
43
44 // Determine which state corresponds to the destination
45 if (source[0] == dest[0]) { // Same row
46 return source[1] == dest[1] ? dp[0] : dp[2]; // Same position or different column
47 }
48 return source[1] == dest[1] ? dp[1] : dp[3]; // Same column or different row & column
49 }
50};
51
1function numberOfWays(n: number, m: number, k: number, source: number[], dest: number[]): number {
2 const MOD = 1e9 + 7;
3
4 // Dynamic programming states:
5 // dp[0]: at source position (same row, same column)
6 // dp[1]: same row as source, different column
7 // dp[2]: different row from source, same column
8 // dp[3]: different row and different column from source
9 let dp: number[] = [0, 0, 0, 0];
10 dp[0] = 1; // Initially at source position
11
12 // Perform k transitions
13 while (k--) {
14 const nextDp: number[] = [0, 0, 0, 0];
15
16 // Transitions to state 0 (back to source position):
17 // - From state 1: choose any of (n-1) other rows to move to source column
18 // - From state 2: choose any of (m-1) other columns to move to source row
19 nextDp[0] = ((n - 1) * dp[1] + (m - 1) * dp[2]) % MOD;
20
21 // Transitions to state 1 (same row, different column):
22 // - From state 0: move to any of (n-1) other rows
23 // - From state 1: stay in same row, move to any of (n-2) other rows (excluding source row)
24 // - From state 3: move to source row from any of (m-1) columns
25 nextDp[1] = (dp[0] + (n - 2) * dp[1] + (m - 1) * dp[3]) % MOD;
26
27 // Transitions to state 2 (different row, same column):
28 // - From state 0: move to any of (m-1) other columns
29 // - From state 2: stay in same column, move to any of (m-2) other columns (excluding source column)
30 // - From state 3: move to source column from any of (n-1) rows
31 nextDp[2] = (dp[0] + (m - 2) * dp[2] + (n - 1) * dp[3]) % MOD;
32
33 // Transitions to state 3 (different row, different column):
34 // - From state 1: move to any of (m-2) other columns (not source column)
35 // - From state 2: move to any of (n-2) other rows (not source row)
36 // - From state 3: stay in different row/column, move to (n-2) other rows or (m-2) other columns
37 nextDp[3] = (dp[1] + dp[2] + (n - 2) * dp[3] + (m - 2) * dp[3]) % MOD;
38
39 dp = nextDp;
40 }
41
42 // Determine which state corresponds to the destination
43 if (source[0] === dest[0]) { // Same row
44 return source[1] === dest[1] ? dp[0] : dp[2]; // Same position or different column
45 }
46 return source[1] === dest[1] ? dp[1] : dp[3]; // Same column or different row & column
47}
48
Time and Space Complexity
The time complexity is O(k)
, where k
is the number of moves. This is because the algorithm runs a single loop that iterates exactly k
times. In each iteration, it performs a constant number of arithmetic operations (additions, multiplications, and modulo operations) to update the four variables a
, b
, c
, and d
. Since each iteration takes O(1)
time and there are k
iterations, the overall time complexity is O(k)
.
The space complexity is O(1)
. The algorithm uses only a fixed number of variables (a
, b
, c
, d
, aa
, bb
, cc
, dd
, and mod
) regardless of the input size. These variables store intermediate results and the final answer, but their number doesn't grow with n
, m
, or k
. The input lists source
and dest
are given as parameters and don't count toward the extra space used by the algorithm itself. Therefore, the space complexity is constant, or O(1)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect State-to-Variable Mapping
One of the most common mistakes is mixing up which variable corresponds to which state, especially when returning the final answer. The variable names and their meanings can be confusing:
Pitfall Example:
# Incorrect mapping when returning the result if source[0] == dest[0]: # Same row if source[1] == dest[1]: # Same position return same_pos else: # Different column but same row return diff_row_same_col # WRONG! Should be same_row_diff_col
Solution: Use clear, consistent naming and add comments to track what each variable represents:
# State definitions (relative to source): # same_pos: at source position # same_row_diff_col: same row as source, different column # diff_row_same_col: different row from source, same column # diff_row_diff_col: different row and different column from source
2. Incorrect Transition Coefficients
Another critical error is using wrong coefficients in state transitions. For example, when calculating transitions to diff_row_same_col
, you might accidentally use (n-2)
instead of (m-2)
:
Pitfall Example:
# Wrong coefficient - using n-2 where m-2 should be used next_diff_row_same_col = (same_pos + (n - 2) * diff_row_same_col + ...) % MOD # Should be (m-2) because we're counting different rows while staying in same column
Solution: Remember the logic for each coefficient:
- When moving within the same row: use
m
(column count) - When moving within the same column: use
n
(row count) - Subtract appropriately based on restrictions (e.g.,
-1
for excluding current position,-2
for excluding both current and target)
3. Forgetting the Modulo Operation
With large values of k
, intermediate calculations can overflow. Forgetting to apply modulo at each step leads to incorrect results:
Pitfall Example:
# Missing modulo - can cause overflow next_same_pos = (n - 1) * same_row_diff_col + (m - 1) * diff_row_same_col
Solution: Apply modulo after every arithmetic operation:
next_same_pos = ((n - 1) * same_row_diff_col + (m - 1) * diff_row_same_col) % MOD
4. Edge Case: Single Row or Column Grid
When n = 1
or m = 1
, some transitions become impossible, and the coefficients can become negative:
Pitfall Example:
# When n = 1, (n-2) becomes -1, causing incorrect calculations next_same_row_diff_col = (same_pos + (n - 2) * same_row_diff_col + ...) % MOD
Solution: Handle edge cases explicitly or ensure coefficients don't go negative:
# Use max(0, coefficient) to prevent negative values
next_same_row_diff_col = (same_pos + max(0, n - 2) * same_row_diff_col + (m - 1) * diff_row_diff_col) % MOD
5. Zero Moves Edge Case
When k = 0
, the answer should be 1 if source equals destination, 0 otherwise. The initialization handles this correctly, but be careful not to enter the loop:
Solution:
The current implementation handles this correctly by initializing same_pos = 1
and running the loop exactly k
times. If k = 0
, the loop doesn't execute, and the final return logic correctly returns 1 for source == dest, 0 otherwise.
Which of these pictures shows the visit order of a depth-first search?
Recommended Readings
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
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
Want a Structured Path to Master System Design Too? Don’t Miss This!