3651. Minimum Cost Path with Teleportations
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 asgrid[x][y] <= grid[i][j](the destination value must not exceed the current cell's value). This move costs0. However, you are limited to using teleportation at mostktimes.
Your task is to compute the minimum total cost required to reach cell (m - 1, n - 1) starting from cell (0, 0).
How We Pick the Algorithm
Why Dynamic Programming?
This problem maps to Dynamic Programming through a short path in the full flowchart.
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 FlowchartIntuition
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:
- 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. - 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] = 0f[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 cost3.)
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 cost2.)
Process value 1 → cells [(0,0), (1,1)]:
- Update
mnover the whole group first:mn = min(2, f[0][0][0]) = min(2, 0) = 0mn = 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. Already3. Stays3.f[1][1][0]: from above(0,0)→0 + 2 = 2. Already2. Stays2.f[1][1][1]: from above(0,1)→3 + 1 = 4; from left(1,0)→2 + 1 = 3. Current value is0, which is smaller. Stays0.
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] = 3f[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))
651class 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}
921class 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};
881// 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}
111Time 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, costingO(mn). - Building the dictionary
gthat maps each value to its list of positions also costsO(mn). - Sorting the distinct keys with
keys = sorted(g, reverse=True)costs at mostO(mn × log(mn)), since there are at mostmndistinct values. - The main loop runs
tfrom1tok. For eacht:- 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).
tcostsO(mn), and overkiterations this contributesO(k × mn). - The teleportation update iterates over all positions grouped by key, which in total visits every cell once, costing
- Combining all parts gives
O(mn) + O(mn × log(mn)) + O(k × mn) = O((k + log(mn)) × mn).
- The initial dynamic programming pass (for
-
Space complexity:
O(k × mn).- The 3-dimensional array
fhas dimensions(k + 1) × m × n, requiringO(k × mn)space. - The dictionary
gstores every cell position exactly once, costingO(mn). - The sorted
keyslist holds at mostmndistinct values, costingO(mn). - The dominant term is the array
f, so the overall space complexity isO(k × mn).
Here,
mandnare the number of rows and columns of the grid, respectively, andkis the maximum allowed number of teleportations. - The 3-dimensional array
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 RoadmapWhat's the relationship between a tree and a graph?
Recommended Readings
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion If you prefer videos here's a video that explains recursion in a fun and easy way Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask him
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity describes how the time needed
Want a Structured Path to Master System Design Too? Don’t Miss This!