796-counting-paths-dp — Counting Paths
Tutorial Video
Text description (accessibility)
This video demonstrates the "796-counting-paths-dp — Counting Paths" functional Rust example. Difficulty level: Fundamental. Key concepts covered: Functional Programming. Counting the number of distinct paths in a grid from top-left to bottom-right (moving right or down) is a combinatorics problem solvable by DP. Key difference from OCaml: 1. **Pascal connection**: `dp[i][j] = dp[i
Tutorial
The Problem
Counting the number of distinct paths in a grid from top-left to bottom-right (moving right or down) is a combinatorics problem solvable by DP. The answer without obstacles is C(m+n-2, m-1) — a binomial coefficient — but obstacles require DP. This problem models robot motion planning, network routing analysis, and combinatorial counting. The obstacle variant uses the same recurrence with zeroed cells.
🎯 Learning Outcomes
unique_paths(m, n) filling dp[i][j] = dp[i-1][j] + dp[i][j-1]unique_paths_obstacles by zeroing blocked cellsC(m+n-2, m-1)Code Example
#![allow(clippy::all)]
//! # Counting Paths
pub fn unique_paths(m: usize, n: usize) -> usize {
let mut dp = vec![vec![1; n]; m];
for i in 1..m {
for j in 1..n {
dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
}
}
dp[m - 1][n - 1]
}
pub fn unique_paths_obstacles(grid: &[Vec<i32>]) -> usize {
if grid.is_empty() || grid[0][0] == 1 {
return 0;
}
let (m, n) = (grid.len(), grid[0].len());
let mut dp = vec![vec![0usize; n]; m];
dp[0][0] = 1;
for j in 1..n {
dp[0][j] = if grid[0][j] == 1 { 0 } else { dp[0][j - 1] };
}
for i in 1..m {
dp[i][0] = if grid[i][0] == 1 { 0 } else { dp[i - 1][0] };
}
for i in 1..m {
for j in 1..n {
dp[i][j] = if grid[i][j] == 1 {
0
} else {
dp[i - 1][j] + dp[i][j - 1]
};
}
}
dp[m - 1][n - 1]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_paths() {
assert_eq!(unique_paths(3, 7), 28);
}
}Key Differences
dp[i][j] = dp[i-1][j] + dp[i][j-1] is Pascal's recurrence rotated 45 degrees; both languages show this connection naturally.dp[j] += dp[j-1] in a left-to-right pass works correctly.OCaml Approach
OCaml implements with Array.make_matrix m n 0 and explicit boundary initialization. The obstacle check grid.(i).(j) = 1 blocks paths. Functional style can express this as let dp = Array.init m (fun i -> Array.init n (fun j -> if i=0 || j=0 then 1 else dp.(i-1).(j) + dp.(i).(j-1))) but requires mutual reference tricks. Pascal's triangle connection: dp[i][j] equals C(i+j, i) in the obstacle-free case.
Full Source
#![allow(clippy::all)]
//! # Counting Paths
pub fn unique_paths(m: usize, n: usize) -> usize {
let mut dp = vec![vec![1; n]; m];
for i in 1..m {
for j in 1..n {
dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
}
}
dp[m - 1][n - 1]
}
pub fn unique_paths_obstacles(grid: &[Vec<i32>]) -> usize {
if grid.is_empty() || grid[0][0] == 1 {
return 0;
}
let (m, n) = (grid.len(), grid[0].len());
let mut dp = vec![vec![0usize; n]; m];
dp[0][0] = 1;
for j in 1..n {
dp[0][j] = if grid[0][j] == 1 { 0 } else { dp[0][j - 1] };
}
for i in 1..m {
dp[i][0] = if grid[i][0] == 1 { 0 } else { dp[i - 1][0] };
}
for i in 1..m {
for j in 1..n {
dp[i][j] = if grid[i][j] == 1 {
0
} else {
dp[i - 1][j] + dp[i][j - 1]
};
}
}
dp[m - 1][n - 1]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_paths() {
assert_eq!(unique_paths(3, 7), 28);
}
}#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_paths() {
assert_eq!(unique_paths(3, 7), 28);
}
}
Deep Comparison
OCaml vs Rust: Counting Paths Dp
Overview
See the example.rs and example.ml files for detailed implementations.
Key Differences
| Aspect | OCaml | Rust |
|---|---|---|
| Type system | Hindley-Milner | Ownership + traits |
| Memory | GC | Zero-cost abstractions |
| Mutability | Explicit ref | mut keyword |
| Error handling | Option/Result | Result<T, E> |
See README.md for detailed comparison.
Exercises
start and end position anywhere in the grid (not restricted to corners), modifying the initialization and boundary conditions.(0,0,0) to (m-1,n-1,p-1) moving only right, down, or forward.