LeetCode 3651 - Minimum Cost Path with Teleportations
The problem asks us to find the minimum cost to travel from the top-left corner (0, 0) of a 2D grid to the bottom-right corner (m - 1, n - 1). Each cell contains a non-negative integer representing its cost. There are two ways to move: 1.
Difficulty: 🔴 Hard
Topics: Array, Dynamic Programming, Matrix
Solution
Problem Understanding
The problem asks us to find the minimum cost to travel from the top-left corner (0, 0) of a 2D grid to the bottom-right corner (m - 1, n - 1). Each cell contains a non-negative integer representing its cost. There are two ways to move:
- Normal move: move either right or down, paying the value of the destination cell.
- Teleportation: jump to any other cell with a value less than or equal to your current cell's value, at zero cost. Teleportation is limited to at most
ktimes.
The input consists of the grid (a 2D array of size m x n) and the integer k. The output is a single integer representing the minimum total cost from start to finish.
Constraints tell us that m and n are small (up to 80), the cell values are moderate (up to 10,000), and the number of teleportations is very limited (0 to 10). This suggests that a brute-force solution exploring all possible paths would be infeasible, especially considering the exponential number of possible teleportation choices.
Important edge cases include:
k = 0, meaning no teleportations are allowed.- A grid where teleportation may not help at all.
- A grid where multiple optimal teleportations exist.
- Smallest grids (
2 x 2) to ensure basic movements are handled.
The problem guarantees valid grid dimensions and non-negative values, so we do not need to handle invalid inputs.
Approaches
Brute Force
A brute-force approach would attempt every possible path from (0, 0) to (m - 1, n - 1), trying all combinations of right, down, and teleportation moves. Each path would be evaluated for cost, and we would return the minimum. While this guarantees correctness, the number of paths grows exponentially with m and n and with the choice of teleportation cells. Given the constraints, this approach is computationally infeasible.
Optimal Approach
The key insight is that we can use dynamic programming with teleportation states combined with BFS or Dijkstra-style traversal. We maintain a DP table dp[i][j][t] representing the minimum cost to reach cell (i, j) using at most t teleportations. We then iterate through the grid, updating cells with normal moves and efficiently applying teleportation moves using sorting by value to ensure only valid teleportation targets are considered.
This reduces the exponential search space to a manageable size, leveraging the limited k (≤ 10) and the grid size (≤ 80 x 80). Essentially, we compute minimum costs in layers of teleportations.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(2^(m+n) * combinations of teleportation) | O(2^(m+n) * combinations of teleportation) | Enumerates all paths including teleportation |
| Optimal | O(m * n * k * log(m * n)) | O(m * n * k) | Uses DP with teleportation layers and efficient value sorting |
Algorithm Walkthrough
- Initialize a 3D DP array
dp[i][j][t]with infinity, whereiandjare coordinates andtis the number of teleportations used. Setdp[0][0][0] = 0. - Preprocess all grid cells into a list of
(value, i, j)and sort ascending by value. This allows us to quickly identify valid teleportation targets. - Iterate through
tfrom0tok. For each cell(i, j):
a. Update dp[i+1][j][t] and dp[i][j+1][t] for normal moves, ensuring indices stay in bounds.
b. If t < k, consider teleportation. All cells (x, y) with grid[x][y] <= grid[i][j] can be reached at zero additional cost. Update dp[x][y][t+1] as min(dp[x][y][t+1], dp[i][j][t]).
4. After processing all teleportations, return the minimum value among dp[m-1][n-1][0..k], representing the minimum cost to reach the destination using up to k teleportations.
Why it works: By iteratively computing the minimum cost to reach each cell with a given number of teleportations, we maintain the invariant that dp[i][j][t] always contains the optimal cost using exactly t teleportations. Sorting cells by value ensures we only teleport to valid targets.
Python Solution
from typing import List
import heapq
class Solution:
def minCost(self, grid: List[List[int]], k: int) -> int:
m, n = len(grid), len(grid[0])
INF = float('inf')
dp = [[[INF] * (k + 1) for _ in range(n)] for _ in range(m)]
dp[0][0][0] = 0
cells = [(grid[i][j], i, j) for i in range(m) for j in range(n)]
cells.sort()
for t in range(k + 1):
for value, i, j in cells:
# normal moves
if i + 1 < m:
dp[i+1][j][t] = min(dp[i+1][j][t], dp[i][j][t] + grid[i+1][j])
if j + 1 < n:
dp[i][j+1][t] = min(dp[i][j+1][t], dp[i][j][t] + grid[i][j+1])
# teleportation
if t < k:
for value2, x, y in cells:
if value2 <= value:
dp[x][y][t+1] = min(dp[x][y][t+1], dp[i][j][t])
else:
break
return min(dp[m-1][n-1])
This implementation follows the DP + teleportation layers strategy. The sorted cells list ensures teleportation updates are valid and efficient. Normal moves update the DP table in place, while teleportation layers propagate costs without exceeding k.
Go Solution
import "math"
func minCost(grid [][]int, k int) int {
m, n := len(grid), len(grid[0])
INF := math.MaxInt32
dp := make([][][]int, m)
for i := 0; i < m; i++ {
dp[i] = make([][]int, n)
for j := 0; j < n; j++ {
dp[i][j] = make([]int, k+1)
for t := 0; t <= k; t++ {
dp[i][j][t] = INF
}
}
}
dp[0][0][0] = 0
type Cell struct { val, x, y int }
cells := make([]Cell, 0, m*n)
for i := 0; i < m; i++ {
for j := 0; j < n; j++ {
cells = append(cells, Cell{grid[i][j], i, j})
}
}
// sort cells by value ascending
sort.Slice(cells, func(a, b int) bool { return cells[a].val < cells[b].val })
for t := 0; t <= k; t++ {
for _, cell := range cells {
i, j := cell.x, cell.y
// normal moves
if i+1 < m {
dp[i+1][j][t] = min(dp[i+1][j][t], dp[i][j][t] + grid[i+1][j])
}
if j+1 < n {
dp[i][j+1][t] = min(dp[i][j+1][t], dp[i][j][t] + grid[i][j+1])
}
// teleportation
if t < k {
for _, c := range cells {
if c.val <= cell.val {
dp[c.x][c.y][t+1] = min(dp[c.x][c.y][t+1], dp[i][j][t])
} else {
break
}
}
}
}
}
ans := INF
for t := 0; t <= k; t++ {
if dp[m-1][n-1][t] < ans {
ans = dp[m-1][n-1][t]
}
}
return ans
}
func min(a, b int) int {
if a < b { return a }
return b
}
Go differences: We must explicitly initialize the 3D slice with INF. We use sort.Slice to order cells by value and helper function min to simplify comparisons.
Worked Examples
Example 1
Grid:
1 3 3
2 5 4
4 3 5
k = 2
Step-by-step:
- Start at
(0,0)cost=0. - Move down to
(1,0)cost=0+2=2.