Facebook Pixel

3651. Minimum Cost Path with Teleportations

Hard
LeetCode ↗

Problem Description

You are given a m x n 2D integer array grid and an integer k. You begin at the top-left cell (0, 0), and your objective is to travel to the bottom-right cell (m - 1, n - 1).

During your journey, two kinds of moves are available to you:

  • Normal move: From your current cell (i, j), you may move one step right to (i, j + 1) or one step down to (i + 1, j). Each such move costs the value stored in the destination cell.

  • Teleportation: From any cell (i, j), you may teleport to any cell (x, y) as long as grid[x][y] <= grid[i][j] (the destination value must not exceed the current cell's value). This move costs 0. However, you are limited to using teleportation at most k times.

Your task is to compute the minimum total cost required to reach cell (m - 1, n - 1) starting from cell (0, 0).

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

How We Pick the Algorithm

Why Dynamic Programming?

This problem maps to Dynamic Programming through a short path in the full flowchart.

Smallconstraints?yesDP ormemoizationneeded?yesDynamicProgramming

The problem uses DP over (row, col, teleports used), grouping cells by value and processing in descending order to find the cheapest teleport source for each destination.

Open in Flowchart

Intuition

The key observation is that the cost we pay depends on two things: where we are in the grid, and how many teleportations we have used so far. Since the number of teleportations is capped at k, the teleport count is a natural extra dimension to track in our state. This naturally points us toward dynamic programming, where we define f[t][i][j] as the minimum cost to reach cell (i, j) having used exactly t teleportations.

Let us think about the two move types separately.

For normal moves, the situation is the classic "minimum path sum" pattern: to arrive at cell (i, j), we must have come from the cell above (i - 1, j) or the cell to the left (i, j - 1), paying the value grid[i][j] to step in. So for a fixed teleport count t, we can fill the grid in order and take the cheaper of the two incoming directions.

The trickier part is teleportation. A teleport lets us jump from cell (i, j) to any cell (x, y) whose value is less than or equal to grid[i][j], and it is free. The question becomes: if we are about to land at a cell with value v via teleport, where is the cheapest place we could have teleported from? It can be any cell whose value is at least v. So among all cells with value >= v, we want the one with the smallest cost reached using t - 1 teleportations.

This is where the sorting by value trick comes in. If we group cells by their value and process the groups in descending order of value, we can maintain a running minimum mn. As we sweep from high values down to low values, mn keeps track of the cheapest f[t - 1] cost among all cells seen so far — and those are exactly the cells with value >= v that could legally teleport to the current group. So each cell in the current group can be reached for cost mn using one more teleport.

Putting it together, for each teleport count t we do two passes:

  1. First, a descending-value sweep to apply teleportation, setting f[t][i][j] = mn, the cheapest cost to arrive here by teleporting from a higher-or-equal valued cell.
  2. Then, a normal grid sweep to layer ordinary right/down moves on top of those teleport landing points.

Finally, since using fewer teleportations might sometimes be cheaper (extra teleports never hurt but the optimal answer could occur at any count), we take the minimum of f[t][m - 1][n - 1] across all t from 0 to k.

Solution Approach

Solution 1: Dynamic Programming

We define f[t][i][j] as the minimum cost to reach cell (i, j) using exactly t teleportations. Initially, f[0][0][0] = 0, and all other states are set to infinity.

Step 1: Initialize the zero-teleport layer.

First, we fill in f[0][i][j], the costs reachable without any teleportation. In this case we can only arrive at a cell by moving right or down.

If i > 0, we can move from the cell above (i - 1, j):

f[0][i][j] = min(f[0][i][j], f[0][i - 1][j] + grid[i][j])

If j > 0, we can move from the cell to the left (i, j - 1):

f[0][i][j] = min(f[0][i][j], f[0][i][j - 1] + grid[i][j])

Step 2: Group cells by value.

To handle teleportation efficiently, we group the grid cells by their value using a hash map g, where the key is the cell value and the value is a list of coordinates of cells having that value. We then collect the keys and sort them in descending order into keys.

Step 3: Process each teleport count.

For each teleport count t from 1 to k, we perform two passes.

The first pass applies teleportation. We maintain a running minimum mn, and iterate over the value groups in descending order. For each group, we first update mn using every cell in the group based on the previous layer:

mn = min(mn, f[t - 1][i][j])

Then we assign that running minimum to every cell in the same group:

f[t][i][j] = mn

The crucial detail here is the two-phase loop within each group: we update mn over the whole group before writing mn back to those cells. This guarantees that a teleport must come from a cell with value strictly larger, or from another cell in the same group, all of which satisfy grid[x][y] <= grid[i][j] — exactly matching the teleport rule. Since groups are processed from high values down to low values, mn always represents the cheapest f[t - 1] cost over all cells whose value is >= the current group's value.

The second pass layers ordinary moves on top of the teleport landing points by sweeping the grid in order:

If i > 0:

f[t][i][j] = min(f[t][i][j], f[t][i - 1][j] + grid[i][j])

If j > 0:

f[t][i][j] = min(f[t][i][j], f[t][i][j - 1] + grid[i][j])

Step 4: Combine the answer.

Since using fewer teleportations might yield a smaller total cost, the final answer is the minimum value of f[t][m - 1][n - 1] over all t from 0 to k:

min(f[t][m - 1][n - 1] for t in range(k + 1))

Complexity Analysis.

Let m and n be the dimensions of the grid. There are k + 1 layers, and each layer requires processing all m * n cells for both the teleport sweep and the normal-move sweep. The time complexity is O(k * m * n) (the value grouping and sorting adds O(m * n * log(m * n)), which is dominated for typical inputs). The space complexity is O(k * m * n) for the f table.

Example Walkthrough

Let's trace through the solution with a small concrete example.

Input:

grid = [[1, 3],
        [2, 1]]
k = 1

We start at (0, 0) (value 1) and want to reach (1, 1) (value 1). We may teleport at most 1 time.


Step 1: Initialize the zero-teleport layer f[0].

We compute minimum path sums using only right/down moves. By convention, f[0][0][0] = 0 (we don't pay for the starting cell).

  • f[0][0][0] = 0
  • f[0][0][1] = f[0][0][0] + grid[0][1] = 0 + 3 = 3 (move right)
  • f[0][1][0] = f[0][0][0] + grid[1][0] = 0 + 2 = 2 (move down)
  • f[0][1][1]: come from above (0,1)3 + 1 = 4, or from left (1,0)2 + 1 = 3. Take the min → 3.

So without teleportation:

f[0] = [[0, 3],
        [2, 3]]

The best cost to reach (1, 1) with 0 teleports is 3.


Step 2: Group cells by value and sort descending.

g = { 1: [(0,0), (1,1)],
      3: [(0,1)],
      2: [(1,0)] }

keys (descending) = [3, 2, 1]

Step 3: Process teleport count t = 1.

First pass — teleport sweep (descending value order). Maintain running minimum mn = ∞.

Process value 3 → cells [(0,1)]:

  • Update mn: mn = min(∞, f[0][0][1]) = min(∞, 3) = 3.
  • Write back: f[1][0][1] = mn = 3. (Teleporting into (0,1) requires coming from a cell with value ≥ 3; only (0,1) itself qualifies so far, giving cost 3.)

Process value 2 → cells [(1,0)]:

  • Update mn: mn = min(3, f[0][1][0]) = min(3, 2) = 2.
  • Write back: f[1][1][0] = mn = 2. (We could teleport into (1,0) from any cell with value ≥ 2, i.e. (0,1) or (1,0); cheapest is cost 2.)

Process value 1 → cells [(0,0), (1,1)]:

  • Update mn over the whole group first:
    • mn = min(2, f[0][0][0]) = min(2, 0) = 0
    • mn = min(0, f[0][1][1]) = min(0, 3) = 0
  • Write back to both cells: f[1][0][0] = 0, f[1][1][1] = 0.

Here's the key insight at (1, 1): we teleported into it for cost 0. The running mn = 0 came from (0, 0), which has value 1 ≥ 1, so the teleport (0,0) → (1,1) is legal and free. We effectively jumped from start to the destination region for 0 cost.

After the teleport sweep:

f[1] = [[0, 3],
        [2, 0]]

Second pass — normal-move sweep. Layer right/down moves on top:

  • f[1][0][0] = 0 (no incoming moves)
  • f[1][0][1]: from left (0,0)0 + 3 = 3. Already 3. Stays 3.
  • f[1][1][0]: from above (0,0)0 + 2 = 2. Already 2. Stays 2.
  • f[1][1][1]: from above (0,1)3 + 1 = 4; from left (1,0)2 + 1 = 3. Current value is 0, which is smaller. Stays 0.

Final layer:

f[1] = [[0, 3],
        [2, 0]]

The best cost to reach (1, 1) with 1 teleport is 0.


Step 4: Combine the answer.

Take the minimum over all teleport counts:

  • f[0][1][1] = 3
  • f[1][1][1] = 0
answer = min(3, 0) = 0

Interpretation: The optimal strategy is to teleport directly from (0, 0) (value 1) to (1, 1) (value 1). Since 1 ≤ 1, the teleport is allowed, it costs 0, and it uses exactly one of our k = 1 allowed teleports. This beats the cheapest walking path (cost 3), so the minimum total cost is 0.

Solution Implementation

1from collections import defaultdict
2from math import inf
3from typing import List
4
5
6class Solution:
7    def minCost(self, grid: List[List[int]], k: int) -> int:
8        rows, cols = len(grid), len(grid[0])
9
10        # cost[t][i][j]: minimum cost to reach cell (i, j) using exactly t teleports.
11        # A teleport jumps to any other cell sharing the same grid value.
12        cost = [[[inf] * cols for _ in range(rows)] for _ in range(k + 1)]
13        cost[0][0][0] = 0
14
15        # Base layer (t = 0): plain grid DP, only moving down or right.
16        for i in range(rows):
17            for j in range(cols):
18                if i:
19                    cost[0][i][j] = min(
20                        cost[0][i][j], cost[0][i - 1][j] + grid[i][j]
21                    )
22                if j:
23                    cost[0][i][j] = min(
24                        cost[0][i][j], cost[0][i][j - 1] + grid[i][j]
25                    )
26
27        # Group cell coordinates by their value so teleports can connect them.
28        value_to_cells = defaultdict(list)
29        for i, row in enumerate(grid):
30            for j, value in enumerate(row):
31                value_to_cells[value].append((i, j))
32
33        # Process distinct values in descending order.
34        sorted_values = sorted(value_to_cells, reverse=True)
35
36        for t in range(1, k + 1):
37            best_prev = inf
38            for value in sorted_values:
39                cells = value_to_cells[value]
40
41                # Find the cheapest cell (from the previous teleport layer)
42                # among all cells sharing this value.
43                for i, j in cells:
44                    best_prev = min(best_prev, cost[t - 1][i][j])
45
46                # Teleport into every same-valued cell at the best prior cost.
47                for i, j in cells:
48                    cost[t][i][j] = best_prev
49
50            # After teleporting, allow normal down/right moves within layer t.
51            for i in range(rows):
52                for j in range(cols):
53                    if i:
54                        cost[t][i][j] = min(
55                            cost[t][i][j], cost[t][i - 1][j] + grid[i][j]
56                        )
57                    if j:
58                        cost[t][i][j] = min(
59                            cost[t][i][j], cost[t][i][j - 1] + grid[i][j]
60                        )
61
62        # Answer is the minimum cost to reach the bottom-right corner
63        # across any number of teleports (0 to k).
64        return min(cost[t][rows - 1][cols - 1] for t in range(k + 1))
65
1class Solution {
2    public int minCost(int[][] grid, int k) {
3        int rows = grid.length, cols = grid[0].length;
4        // Use a "large enough" value to represent infinity while avoiding overflow during addition
5        int inf = Integer.MAX_VALUE / 2;
6
7        // dp[t][i][j] = minimum cost to reach cell (i, j) using exactly (at most) t teleports
8        int[][][] dp = new int[k + 1][rows][cols];
9        for (int t = 0; t <= k; t++) {
10            for (int i = 0; i < rows; i++) {
11                Arrays.fill(dp[t][i], inf);
12            }
13        }
14
15        // --- Base case: t = 0 (no teleports used) ---
16        // Starting point cost is 0
17        dp[0][0][0] = 0;
18        for (int i = 0; i < rows; i++) {
19            for (int j = 0; j < cols; j++) {
20                // Move down from the cell above
21                if (i > 0) {
22                    dp[0][i][j] = Math.min(dp[0][i][j], dp[0][i - 1][j] + grid[i][j]);
23                }
24                // Move right from the cell on the left
25                if (j > 0) {
26                    dp[0][i][j] = Math.min(dp[0][i][j], dp[0][i][j - 1] + grid[i][j]);
27                }
28            }
29        }
30
31        // --- Group cell coordinates by their grid value ---
32        // A teleport can jump between any two cells that share the same value
33        Map<Integer, List<int[]>> valueToCells = new HashMap<>();
34        for (int i = 0; i < rows; i++) {
35            for (int j = 0; j < cols; j++) {
36                int value = grid[i][j];
37                valueToCells.computeIfAbsent(value, key -> new ArrayList<>())
38                            .add(new int[] {i, j});
39            }
40        }
41
42        // Sort the distinct values in descending order.
43        // Processing larger values first lets us carry forward the running minimum,
44        // so a teleport from a higher value can reach any cell with a value <= it.
45        List<Integer> sortedValues = new ArrayList<>(valueToCells.keySet());
46        sortedValues.sort(Collections.reverseOrder());
47
48        // --- Transition for each teleport count t (1..k) ---
49        for (int t = 1; t <= k; t++) {
50            // runningMin tracks the best dp value (using t-1 teleports) among all cells
51            // whose value is >= the current value group being processed.
52            int runningMin = inf;
53            for (int value : sortedValues) {
54                List<int[]> cells = valueToCells.get(value);
55
56                // Update the running minimum with the best cost to arrive at any cell
57                // in this value group using t-1 teleports.
58                for (int[] cell : cells) {
59                    runningMin = Math.min(runningMin, dp[t - 1][cell[0]][cell[1]]);
60                }
61
62                // Teleporting into any cell of this group costs the same as the
63                // accumulated minimum (no movement cost for the teleport itself).
64                for (int[] cell : cells) {
65                    dp[t][cell[0]][cell[1]] = runningMin;
66                }
67            }
68
69            // After applying teleports, relax with normal down/right moves
70            // so the teleport landing spots can propagate further.
71            for (int i = 0; i < rows; i++) {
72                for (int j = 0; j < cols; j++) {
73                    if (i > 0) {
74                        dp[t][i][j] = Math.min(dp[t][i][j], dp[t][i - 1][j] + grid[i][j]);
75                    }
76                    if (j > 0) {
77                        dp[t][i][j] = Math.min(dp[t][i][j], dp[t][i][j - 1] + grid[i][j]);
78                    }
79                }
80            }
81        }
82
83        // --- Collect the answer ---
84        // The optimal solution may use anywhere from 0 to k teleports.
85        int ans = inf;
86        for (int t = 0; t <= k; t++) {
87            ans = Math.min(ans, dp[t][rows - 1][cols - 1]);
88        }
89        return ans;
90    }
91}
92
1class Solution {
2public:
3    int minCost(vector<vector<int>>& grid, int k) {
4        int rows = grid.size(), cols = grid[0].size();
5        const int kInf = INT_MAX / 2;
6
7        // dp[t][i][j]: minimum cost to reach cell (i, j) using exactly t teleport operations.
8        vector<vector<vector<int>>> dp(
9            k + 1, vector<vector<int>>(rows, vector<int>(cols, kInf)));
10
11        // Base case: starting cell with zero teleports.
12        dp[0][0][0] = 0;
13
14        // Fill the layer with 0 teleports using only normal down/right moves.
15        for (int i = 0; i < rows; i++) {
16            for (int j = 0; j < cols; j++) {
17                if (i > 0) {
18                    dp[0][i][j] = min(dp[0][i][j], dp[0][i - 1][j] + grid[i][j]);
19                }
20                if (j > 0) {
21                    dp[0][i][j] = min(dp[0][i][j], dp[0][i][j - 1] + grid[i][j]);
22                }
23            }
24        }
25
26        // Group every cell coordinate by its grid value.
27        unordered_map<int, vector<pair<int, int>>> valueToCells;
28        for (int i = 0; i < rows; i++) {
29            for (int j = 0; j < cols; j++) {
30                int value = grid[i][j];
31                valueToCells[value].push_back({i, j});
32            }
33        }
34
35        // Collect all distinct values and sort them in descending order so that
36        // cheaper buckets can inherit the minimum cost from more expensive ones.
37        vector<int> sortedKeys;
38        sortedKeys.reserve(valueToCells.size());
39        for (auto& entry : valueToCells) {
40            sortedKeys.push_back(entry.first);
41        }
42        sort(sortedKeys.begin(), sortedKeys.end(), greater<int>());
43
44        // Compute each teleport layer t from 1 to k.
45        for (int t = 1; t <= k; t++) {
46            int runningMin = kInf;
47
48            // Process value buckets from largest to smallest value.
49            // A teleport into a bucket can come from any cell with a value
50            // greater than or equal to it (already merged into runningMin).
51            for (int key : sortedKeys) {
52                auto& cells = valueToCells[key];
53
54                // Update the running minimum with the best cost (using t-1 teleports)
55                // among all cells in the current bucket.
56                for (auto& cell : cells) {
57                    runningMin = min(runningMin, dp[t - 1][cell.first][cell.second]);
58                }
59
60                // Teleport into every cell of this bucket with the running minimum cost.
61                for (auto& cell : cells) {
62                    dp[t][cell.first][cell.second] = runningMin;
63                }
64            }
65
66            // After teleporting, relax this layer again with normal down/right moves.
67            for (int i = 0; i < rows; i++) {
68                for (int j = 0; j < cols; j++) {
69                    if (i > 0) {
70                        dp[t][i][j] = min(dp[t][i][j], dp[t][i - 1][j] + grid[i][j]);
71                    }
72                    if (j > 0) {
73                        dp[t][i][j] = min(dp[t][i][j], dp[t][i][j - 1] + grid[i][j]);
74                    }
75                }
76            }
77        }
78
79        // The answer is the minimum cost to reach the bottom-right cell
80        // using any number of teleports from 0 to k.
81        int answer = kInf;
82        for (int t = 0; t <= k; t++) {
83            answer = min(answer, dp[t][rows - 1][cols - 1]);
84        }
85        return answer;
86    }
87};
88
1// The smallest possible cost; used as a sentinel for "unreachable" states.
2const INF = 1e18;
3
4/**
5 * Computes the minimum cost to travel from the top-left cell to the
6 * bottom-right cell of `grid`, where at most `k` "teleport" operations may be
7 * used.
8 *
9 * Movement rules:
10 *  - You may move from a cell to an adjacent cell (down or right), paying the
11 *    cost of the cell you move into.
12 *  - You may also "teleport" from any cell to any other cell that has a
13 *    strictly smaller (or equal, handled greedily) grid value for free, but
14 *    each such teleport consumes one of the `k` allowed operations.
15 *
16 * The dynamic programming state is:
17 *   f[t][i][j] = minimum cost to reach cell (i, j) having used exactly `t`
18 *                teleport operations.
19 *
20 * @param grid The 2D cost grid.
21 * @param k    The maximum number of teleport operations allowed.
22 * @returns    The minimum total cost to reach the bottom-right corner.
23 */
24function minCost(grid: number[][], k: number): number {
25    // Grid dimensions: `rows` x `cols`.
26    const [rows, cols] = [grid.length, grid[0].length];
27
28    // f[t][i][j] = min cost to reach (i, j) using exactly `t` teleports.
29    // Initialize every state to INF (unreachable).
30    const f: number[][][] = Array.from({ length: k + 1 }, () =>
31        Array.from({ length: rows }, () => Array<number>(cols).fill(INF)),
32    );
33
34    // Base case: starting cell with zero teleports costs nothing.
35    f[0][0][0] = 0;
36
37    // Layer t = 0: pure walking (down/right) with no teleports.
38    for (let i = 0; i < rows; i++) {
39        for (let j = 0; j < cols; j++) {
40            // Arrive from the cell above.
41            if (i > 0) {
42                f[0][i][j] = Math.min(f[0][i][j], f[0][i - 1][j] + grid[i][j]);
43            }
44            // Arrive from the cell on the left.
45            if (j > 0) {
46                f[0][i][j] = Math.min(f[0][i][j], f[0][i][j - 1] + grid[i][j]);
47            }
48        }
49    }
50
51    // Group cell coordinates by their grid value so teleport targets sharing
52    // a value can be processed together.
53    const valueToPositions = new Map<number, Array<[number, number]>>();
54    for (let i = 0; i < rows; i++) {
55        for (let j = 0; j < cols; j++) {
56            const value = grid[i][j];
57            if (!valueToPositions.has(value)) {
58                valueToPositions.set(value, []);
59            }
60            valueToPositions.get(value)!.push([i, j]);
61        }
62    }
63
64    // Distinct grid values sorted in descending order. Processing from large
65    // to small lets us accumulate the best previously-reachable cost (`best`)
66    // and propagate it to cells with smaller values via a teleport.
67    const sortedValues = Array.from(valueToPositions.keys()).sort((a, b) => b - a);
68
69    // Build each teleport layer t = 1 .. k.
70    for (let t = 1; t <= k; t++) {
71        // Best cost found so far among cells with a value >= the current one,
72        // taken from the previous teleport layer.
73        let best = INF;
74
75        for (const value of sortedValues) {
76            const positions = valueToPositions.get(value)!;
77
78            // Update `best` using all cells of this value from layer t - 1.
79            for (const [x, y] of positions) {
80                best = Math.min(best, f[t - 1][x][y]);
81            }
82            // Teleport into every cell of this value at cost `best`.
83            for (const [x, y] of positions) {
84                f[t][x][y] = best;
85            }
86        }
87
88        // After teleporting, allow normal walking within this layer.
89        for (let i = 0; i < rows; i++) {
90            for (let j = 0; j < cols; j++) {
91                // Arrive from the cell above.
92                if (i > 0) {
93                    f[t][i][j] = Math.min(f[t][i][j], f[t][i - 1][j] + grid[i][j]);
94                }
95                // Arrive from the cell on the left.
96                if (j > 0) {
97                    f[t][i][j] = Math.min(f[t][i][j], f[t][i][j - 1] + grid[i][j]);
98                }
99            }
100        }
101    }
102
103    // The answer is the minimum cost to reach the bottom-right corner using
104    // any number of teleports from 0 to k.
105    let ans = INF;
106    for (let t = 0; t <= k; t++) {
107        ans = Math.min(ans, f[t][rows - 1][cols - 1]);
108    }
109    return ans;
110}
111

Time and Space Complexity

  • Time complexity: O((k + log(mn)) × mn).

    • The initial dynamic programming pass (for t = 0) iterates over every cell of the grid once, costing O(mn).
    • Building the dictionary g that maps each value to its list of positions also costs O(mn).
    • Sorting the distinct keys with keys = sorted(g, reverse=True) costs at most O(mn × log(mn)), since there are at most mn distinct values.
    • The main loop runs t from 1 to k. For each t:
      • The teleportation update iterates over all positions grouped by key, which in total visits every cell once, costing O(mn).
      • The subsequent walking DP over the whole grid also costs O(mn).
      Hence each iteration of t costs O(mn), and over k iterations this contributes O(k × mn).
    • Combining all parts gives O(mn) + O(mn × log(mn)) + O(k × mn) = O((k + log(mn)) × mn).
  • Space complexity: O(k × mn).

    • The 3-dimensional array f has dimensions (k + 1) × m × n, requiring O(k × mn) space.
    • The dictionary g stores every cell position exactly once, costing O(mn).
    • The sorted keys list holds at most mn distinct values, costing O(mn).
    • The dominant term is the array f, so the overall space complexity is O(k × mn).

    Here, m and n are the number of rows and columns of the grid, respectively, and k is the maximum allowed number of teleportations.

Common Pitfalls

Pitfall 1: Misunderstanding the Teleport Rule — Using "Equal Value" Instead of "Less Than or Equal"

The most dangerous trap in this problem lies in the two-phase loop inside each value group. A careless reading of the code's comments (or a naive re-implementation) suggests teleportation only connects cells that share the exact same value. But the actual problem states teleportation is allowed whenever grid[x][y] <= grid[i][j], i.e., you may jump to any cell whose value is less than or equal to your current cell.

The correctness of the solution hinges entirely on processing groups in descending order of value while carrying a running minimum best_prev across groups. If you reset best_prev for each group (treating teleports as "same value only"), you break the algorithm.

Incorrect version:

for t in range(1, k + 1):
    for value in sorted_values:
        cells = value_to_cells[value]
        best_prev = inf          # WRONG: reset inside the loop
        for i, j in cells:
            best_prev = min(best_prev, cost[t - 1][i][j])
        for i, j in cells:
            cost[t][i][j] = best_prev
    ...

This only lets a cell teleport from another cell of the identical value, ignoring all higher-valued cells that are legal teleport sources. The resulting cost will be too high (or inf when no same-value path exists).

Correct version:

for t in range(1, k + 1):
    best_prev = inf              # CORRECT: declared once, outside the value loop
    for value in sorted_values:  # descending order is essential
        cells = value_to_cells[value]
        for i, j in cells:
            best_prev = min(best_prev, cost[t - 1][i][j])  # phase 1: absorb
        for i, j in cells:
            cost[t][i][j] = best_prev                       # phase 2: write
    ...

Because best_prev persists across groups and the groups descend in value, by the time we reach value v, best_prev already incorporates the cheapest cost[t-1] over every cell with value >= v — exactly the set of legal teleport sources.

Pitfall 2: Collapsing the Two Phases Into One Loop

Even with best_prev correctly placed outside the group loop, merging the "absorb" and "write" passes into a single loop is subtly wrong:

for value in sorted_values:
    for i, j in value_to_cells[value]:
        best_prev = min(best_prev, cost[t - 1][i][j])
        cost[t][i][j] = best_prev      # WRONG: uses partial group minimum

Here, the first cell in a group writes a best_prev that hasn't yet seen the other cells of the same value. Since same-value cells are also valid teleport sources (v <= v), every cell in the group should land on the full group minimum. The two-phase split guarantees all same-valued cells receive an identical, complete minimum.

Pitfall 3: Forgetting That Fewer Teleports May Be Cheaper

The DP layer cost[t] represents reaching a cell with exactly t teleports, not "at most" t. A teleport is free but forces you to a landing cell that may sit behind a cheaper non-teleport path. Returning only cost[k][rows-1][cols-1] is a mistake:

return cost[k][rows - 1][cols - 1]   # WRONG: forces exactly k teleports

You must take the minimum across all teleport counts:

return min(cost[t][rows - 1][cols - 1] for t in range(k + 1))

Pitfall 4: Memory Blow-Up From the Full 3D Table

The O(k * m * n) space can be prohibitive for large k. Notice that layer t depends only on layer t - 1. You can roll the DP onto two 2D arrays (prev and curr), reducing space to O(m * n), while tracking the running answer for the bottom-right cell as you finish each layer:

prev = [[inf] * cols for _ in range(rows)]
prev[0][0] = 0
# fill prev with the t=0 base DP ...
answer = prev[rows - 1][cols - 1]

for t in range(1, k + 1):
    curr = [[inf] * cols for _ in range(rows)]
    best_prev = inf
    for value in sorted_values:
        cells = value_to_cells[value]
        for i, j in cells:
            best_prev = min(best_prev, prev[i][j])
        for i, j in cells:
            curr[i][j] = best_prev
    for i in range(rows):
        for j in range(cols):
            if i:
                curr[i][j] = min(curr[i][j], curr[i - 1][j] + grid[i][j])
            if j:
                curr[i][j] = min(curr[i][j], curr[i][j - 1] + grid[i][j])
    answer = min(answer, curr[rows - 1][cols - 1])
    prev = curr

return answer

This preserves the same time complexity while shrinking memory from O(k * m * n) to O(m * n).

Ready to land your dream job?

Unlock your dream job with a 5-minute quiz for a personalized study roadmap!

Get My Roadmap
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Get a Personalized Study Roadmap:

What's the relationship between a tree and a graph?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More