Skip to content

Commit 568edb0

Browse files
committed
Implemented the Minimum Path Sum algorithm in R.
Finds the minimum sum path from top-left to bottom-right corner using dynamic programming. Efficient O(m*n) DP implementation with O(min(m,n)) space-optimized version. Handles edge cases including single row/column grids and finds all minimum paths. Prints DP table, path visualization, and performance comparisons. Time Complexity: O(m * n) where m = rows, n = columns. Space Complexity: O(m * n) for DP table, O(min(m, n)) for optimized version.
1 parent 72ad1cc commit 568edb0

1 file changed

Lines changed: 366 additions & 0 deletions

File tree

Lines changed: 366 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,366 @@
1+
# Minimum Path Sum Problem
2+
#
3+
# The Minimum Path Sum problem finds the minimum sum path from the top-left corner
4+
# to the bottom-right corner of a grid, moving only right or down at each step.
5+
# This is a classic dynamic programming problem that appears in various forms.
6+
#
7+
# Time Complexity: O(m * n) where m = number of rows, n = number of columns
8+
# Space Complexity: O(m * n) for DP table, O(min(m, n)) for optimized version
9+
#
10+
# Applications:
11+
# - Grid-based pathfinding algorithms
12+
# - Resource optimization in 2D grids
13+
# - Game development (pathfinding with costs)
14+
# - Network routing optimization
15+
# - Cost minimization in transportation
16+
17+
# Basic DP solution for Minimum Path Sum
18+
minimum_path_sum <- function(grid) {
19+
#' Find the minimum path sum from top-left to bottom-right corner
20+
#' @param grid: 2D matrix of non-negative integers
21+
#' @return: List containing minimum sum, path, and DP table
22+
23+
m <- nrow(grid)
24+
n <- ncol(grid)
25+
26+
# Handle edge case
27+
if (m == 0 || n == 0) {
28+
return(list(
29+
min_sum = 0,
30+
path = c(),
31+
dp_table = matrix(0, nrow = 1, ncol = 1)
32+
))
33+
}
34+
35+
# Create DP table: dp[i, j] = minimum sum to reach position (i, j)
36+
dp <- matrix(0, nrow = m, ncol = n)
37+
38+
# Initialize first row and column
39+
dp[1, 1] <- grid[1, 1]
40+
41+
# Fill first row (can only move right)
42+
if (n > 1) {
43+
for (j in 2:n) {
44+
dp[1, j] <- dp[1, j - 1] + grid[1, j]
45+
}
46+
}
47+
48+
# Fill first column (can only move down)
49+
if (m > 1) {
50+
for (i in 2:m) {
51+
dp[i, 1] <- dp[i - 1, 1] + grid[i, 1]
52+
}
53+
}
54+
55+
# Fill remaining cells
56+
if (m > 1 && n > 1) {
57+
for (i in 2:m) {
58+
for (j in 2:n) {
59+
dp[i, j] <- min(dp[i - 1, j], dp[i, j - 1]) + grid[i, j]
60+
}
61+
}
62+
}
63+
64+
# Backtrack to find the path
65+
path <- list()
66+
i <- m
67+
j <- n
68+
69+
while (i > 1 || j > 1) {
70+
path <- c(list(c(i, j)), path)
71+
72+
if (i == 1) {
73+
# Can only move left
74+
j <- j - 1
75+
} else if (j == 1) {
76+
# Can only move up
77+
i <- i - 1
78+
} else {
79+
# Choose direction with minimum sum
80+
if (dp[i - 1, j] < dp[i, j - 1]) {
81+
i <- i - 1
82+
} else {
83+
j <- j - 1
84+
}
85+
}
86+
}
87+
path <- c(list(c(1, 1)), path)
88+
89+
return(list(
90+
min_sum = dp[m, n],
91+
path = path,
92+
dp_table = dp
93+
))
94+
}
95+
96+
# Space-optimized version using only 1D array
97+
minimum_path_sum_optimized <- function(grid) {
98+
#' Space optimized minimum path sum using 1D array
99+
#' @param grid: 2D matrix of non-negative integers
100+
#' @return: Minimum path sum
101+
102+
m <- nrow(grid)
103+
n <- ncol(grid)
104+
105+
if (m == 0 || n == 0) return(0)
106+
107+
# Use the smaller dimension for space optimization
108+
if (m <= n) {
109+
# Process row by row
110+
dp <- rep(0, m)
111+
dp[1] <- grid[1, 1]
112+
113+
# Initialize first row
114+
for (i in 2:m) {
115+
dp[i] <- dp[i - 1] + grid[i, 1]
116+
}
117+
118+
# Process remaining columns
119+
for (j in 2:n) {
120+
dp[1] <- dp[1] + grid[1, j]
121+
for (i in 2:m) {
122+
dp[i] <- min(dp[i - 1], dp[i]) + grid[i, j]
123+
}
124+
}
125+
} else {
126+
# Process column by column
127+
dp <- rep(0, n)
128+
dp[1] <- grid[1, 1]
129+
130+
# Initialize first column
131+
for (j in 2:n) {
132+
dp[j] <- dp[j - 1] + grid[1, j]
133+
}
134+
135+
# Process remaining rows
136+
for (i in 2:m) {
137+
dp[1] <- dp[1] + grid[i, 1]
138+
for (j in 2:n) {
139+
dp[j] <- min(dp[j - 1], dp[j]) + grid[i, j]
140+
}
141+
}
142+
}
143+
144+
return(dp[length(dp)])
145+
}
146+
147+
# Function to find all possible minimum paths
148+
find_all_minimum_paths <- function(grid) {
149+
#' Find all possible paths that achieve the minimum sum
150+
#' @param grid: 2D matrix of non-negative integers
151+
#' @return: List of all minimum cost paths
152+
153+
m <- nrow(grid)
154+
n <- ncol(grid)
155+
156+
if (m == 0 || n == 0) return(list())
157+
158+
# First compute the minimum sum
159+
result <- minimum_path_sum(grid)
160+
min_sum <- result$min_sum
161+
162+
all_paths <- list()
163+
164+
# Use recursive backtracking to find all paths with minimum sum
165+
find_paths_recursive <- function(current_path, current_sum, i, j) {
166+
current_sum <- current_sum + grid[i, j]
167+
168+
# If we've reached the bottom-right corner
169+
if (i == m && j == n) {
170+
if (current_sum == min_sum) {
171+
all_paths <<- c(all_paths, list(c(current_path, list(c(i, j)))))
172+
}
173+
return
174+
}
175+
176+
# If current sum exceeds minimum, prune
177+
if (current_sum > min_sum) {
178+
return
179+
}
180+
181+
# Move right
182+
if (j < n) {
183+
find_paths_recursive(c(current_path, list(c(i, j))), current_sum, i, j + 1)
184+
}
185+
186+
# Move down
187+
if (i < m) {
188+
find_paths_recursive(c(current_path, list(c(i, j))), current_sum, i + 1, j)
189+
}
190+
}
191+
192+
find_paths_recursive(list(), 0, 1, 1)
193+
return(all_paths)
194+
}
195+
196+
# Helper function to print DP table
197+
print_minimum_path_sum_dp <- function(dp_table, grid) {
198+
m <- nrow(grid)
199+
n <- ncol(grid)
200+
201+
cat("DP Table for Minimum Path Sum:\n")
202+
cat("Grid:\n")
203+
for (i in 1:m) {
204+
cat(" ")
205+
for (j in 1:n) {
206+
cat(sprintf("%3d ", grid[i, j]))
207+
}
208+
cat("\n")
209+
}
210+
cat("\nDP Table:\n")
211+
for (i in 1:m) {
212+
cat(" ")
213+
for (j in 1:n) {
214+
cat(sprintf("%3d ", dp_table[i, j]))
215+
}
216+
cat("\n")
217+
}
218+
cat("\n")
219+
}
220+
221+
# Helper function to visualize path on grid
222+
visualize_path <- function(grid, path) {
223+
m <- nrow(grid)
224+
n <- ncol(grid)
225+
226+
cat("Path Visualization:\n")
227+
cat("Grid with path marked (*):\n")
228+
229+
# Create a matrix to mark the path
230+
path_matrix <- matrix(" ", nrow = m, ncol = n)
231+
232+
for (pos in path) {
233+
path_matrix[pos[1], pos[2]] <- "*"
234+
}
235+
236+
for (i in 1:m) {
237+
cat(" ")
238+
for (j in 1:n) {
239+
if (path_matrix[i, j] == "*") {
240+
cat(sprintf("%3s ", "*"))
241+
} else {
242+
cat(sprintf("%3d ", grid[i, j]))
243+
}
244+
}
245+
cat("\n")
246+
}
247+
cat("\n")
248+
}
249+
250+
# ===========================
251+
# Example Usage & Testing
252+
# ===========================
253+
cat("=== Minimum Path Sum Problem (Dynamic Programming) ===\n\n")
254+
255+
# Test 1: Basic Example
256+
cat("Test 1: Basic Example\n")
257+
grid1 <- matrix(c(1, 3, 1, 1, 5, 1, 4, 2, 1), nrow = 3, ncol = 3, byrow = TRUE)
258+
cat("Grid:\n")
259+
print(grid1)
260+
261+
result1 <- minimum_path_sum(grid1)
262+
print_minimum_path_sum_dp(result1$dp_table, grid1)
263+
cat("Minimum Path Sum:", result1$min_sum, "\n")
264+
cat("Path (row, col):", paste(sapply(result1$path, function(x) paste("(", x[1], ",", x[2], ")", sep="")), collapse = " -> "), "\n")
265+
visualize_path(grid1, result1$path)
266+
cat("\n")
267+
268+
# Test 2: Optimized Version
269+
cat("Test 2: Space Optimized Version\n")
270+
min_sum_opt <- minimum_path_sum_optimized(grid1)
271+
cat("Minimum Path Sum (Optimized):", min_sum_opt, "\n")
272+
cat("Verification: Both methods match:", result1$min_sum == min_sum_opt, "\n\n")
273+
274+
# Test 3: Single Row/Column Cases
275+
cat("Test 3: Edge Cases\n")
276+
cat("Single row grid:\n")
277+
grid_row <- matrix(c(1, 2, 3, 4, 5), nrow = 1)
278+
print(grid_row)
279+
result_row <- minimum_path_sum(grid_row)
280+
cat("Minimum sum:", result_row$min_sum, "\n\n")
281+
282+
cat("Single column grid:\n")
283+
grid_col <- matrix(c(1, 2, 3, 4, 5), ncol = 1)
284+
print(grid_col)
285+
result_col <- minimum_path_sum(grid_col)
286+
cat("Minimum sum:", result_col$min_sum, "\n\n")
287+
288+
# Test 4: Larger Grid
289+
cat("Test 4: Larger Grid (4x5)\n")
290+
set.seed(42)
291+
grid_large <- matrix(sample(1:9, 20, replace = TRUE), nrow = 4, ncol = 5)
292+
cat("Grid:\n")
293+
print(grid_large)
294+
295+
result_large <- minimum_path_sum(grid_large)
296+
cat("Minimum Path Sum:", result_large$min_sum, "\n")
297+
cat("Path length:", length(result_large$path), "steps\n")
298+
visualize_path(grid_large, result_large$path)
299+
cat("\n")
300+
301+
# Test 5: Performance Comparison
302+
cat("Test 5: Performance Comparison (6x8 grid)\n")
303+
grid_perf <- matrix(sample(1:20, 48, replace = TRUE), nrow = 6, ncol = 8)
304+
305+
start_time <- Sys.time()
306+
result_std <- minimum_path_sum(grid_perf)
307+
std_time <- as.numeric(Sys.time() - start_time, units = "secs")
308+
309+
start_time <- Sys.time()
310+
result_opt <- minimum_path_sum_optimized(grid_perf)
311+
opt_time <- as.numeric(Sys.time() - start_time, units = "secs")
312+
313+
cat("Standard DP result:", result_std$min_sum, "\n")
314+
cat("Optimized DP result:", result_opt, "\n")
315+
cat("Standard DP time:", sprintf("%.4f sec", std_time), "\n")
316+
cat("Optimized DP time:", sprintf("%.4f sec", opt_time), "\n")
317+
cat("Results match:", result_std$min_sum == result_opt, "\n\n")
318+
319+
# Test 6: Multiple Minimum Paths
320+
cat("Test 6: Multiple Minimum Paths\n")
321+
grid_multiple <- matrix(c(1, 2, 1, 1, 1, 1, 1, 1, 1), nrow = 3, ncol = 3, byrow = TRUE)
322+
cat("Grid:\n")
323+
print(grid_multiple)
324+
325+
result_multiple <- minimum_path_sum(grid_multiple)
326+
cat("Minimum Path Sum:", result_multiple$min_sum, "\n")
327+
cat("One possible path:", paste(sapply(result_multiple$path, function(x) paste("(", x[1], ",", x[2], ")", sep="")), collapse = " -> "), "\n")
328+
329+
# Find all minimum paths
330+
all_paths <- find_all_minimum_paths(grid_multiple)
331+
cat("Total number of minimum paths:", length(all_paths), "\n")
332+
for (i in seq_along(all_paths)) {
333+
path_str <- paste(sapply(all_paths[[i]], function(x) paste("(", x[1], ",", x[2], ")", sep="")), collapse = " -> ")
334+
path_sum <- sum(sapply(all_paths[[i]], function(x) grid_multiple[x[1], x[2]]))
335+
cat("Path", i, ":", path_str, "(sum =", path_sum, ")\n")
336+
}
337+
cat("\n")
338+
339+
# Test 7: Real-world Example - Cost Optimization
340+
cat("Test 7: Real-world Example - Transportation Cost Optimization\n")
341+
# Grid representing transportation costs between cities
342+
transport_grid <- matrix(c(
343+
2, 3, 4, 2, 1,
344+
1, 2, 1, 3, 2,
345+
3, 1, 2, 1, 4,
346+
2, 4, 1, 2, 3
347+
), nrow = 4, ncol = 5, byrow = TRUE)
348+
349+
cat("Transportation Cost Grid:\n")
350+
print(transport_grid)
351+
352+
transport_result <- minimum_path_sum(transport_grid)
353+
cat("Minimum transportation cost:", transport_result$min_sum, "\n")
354+
cat("Optimal route:", paste(sapply(transport_result$path, function(x) paste("City(", x[1], ",", x[2], ")", sep="")), collapse = " -> "), "\n")
355+
visualize_path(transport_grid, transport_result$path)
356+
357+
# Calculate cost breakdown
358+
cat("Cost breakdown:\n")
359+
total_cost <- 0
360+
for (i in seq_along(transport_result$path)) {
361+
pos <- transport_result$path[[i]]
362+
cost <- transport_grid[pos[1], pos[2]]
363+
total_cost <- total_cost + cost
364+
cat(" Step", i, ": City(", pos[1], ",", pos[2], ") =", cost, "\n")
365+
}
366+
cat("Total cost verification:", total_cost, "\n")

0 commit comments

Comments
 (0)