791-palindrome-partitioning — Palindrome Partitioning
Tutorial Video
Text description (accessibility)
This video demonstrates the "791-palindrome-partitioning — Palindrome Partitioning" functional Rust example. Difficulty level: Advanced. Key concepts covered: Functional Programming. Palindrome partitioning asks: what is the minimum number of cuts needed to divide a string into parts where each part is a palindrome? Key difference from OCaml: 1. **Two
Tutorial
The Problem
Palindrome partitioning asks: what is the minimum number of cuts needed to divide a string into parts where each part is a palindrome? This combines two DP subproblems: checking if substrings are palindromes (interval DP) and finding minimum cuts (linear DP). It appears in DNA restriction site analysis, string compression algorithms, and has connections to the Longest Palindromic Subsequence problem.
🎯 Learning Outcomes
is_pal[i][j] for all substring palindrome checksis_pal[i][j] = chars[i]==chars[j] && is_pal[i+1][j-1]dp[i] = min over j: dp[j-1] + 1 when is_pal[j][i]Code Example
#![allow(clippy::all)]
//! # Palindrome Partitioning
pub fn min_cuts(s: &str) -> usize {
let chars: Vec<char> = s.chars().collect();
let n = chars.len();
if n <= 1 {
return 0;
}
let mut is_pal = vec![vec![false; n]; n];
for i in 0..n {
is_pal[i][i] = true;
}
for i in 0..n - 1 {
is_pal[i][i + 1] = chars[i] == chars[i + 1];
}
for len in 3..=n {
for i in 0..=n - len {
let j = i + len - 1;
is_pal[i][j] = chars[i] == chars[j] && is_pal[i + 1][j - 1];
}
}
let mut dp = vec![0; n];
for i in 0..n {
if is_pal[0][i] {
dp[i] = 0;
} else {
dp[i] = i;
for j in 1..=i {
if is_pal[j][i] {
dp[i] = dp[i].min(dp[j - 1] + 1);
}
}
}
}
dp[n - 1]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cuts() {
assert_eq!(min_cuts("aab"), 1);
}
#[test]
fn test_palindrome() {
assert_eq!(min_cuts("aa"), 0);
}
}Key Differences
dp[i] = i initialization bounds cuts; OCaml uses the same; careful initialization avoids off-by-one errors.OCaml Approach
OCaml implements both DP tables with Array.make_matrix. The palindrome expansion is idiomatic with nested for loops. The minimum cuts DP uses Array.init for initialization and imperative updates. A recursive approach with memoization is also natural in OCaml: min_cuts s i = min over j in 0..i: [if is_pal s j i then 1 + min_cuts s (j-1) else infinity].
Full Source
#![allow(clippy::all)]
//! # Palindrome Partitioning
pub fn min_cuts(s: &str) -> usize {
let chars: Vec<char> = s.chars().collect();
let n = chars.len();
if n <= 1 {
return 0;
}
let mut is_pal = vec![vec![false; n]; n];
for i in 0..n {
is_pal[i][i] = true;
}
for i in 0..n - 1 {
is_pal[i][i + 1] = chars[i] == chars[i + 1];
}
for len in 3..=n {
for i in 0..=n - len {
let j = i + len - 1;
is_pal[i][j] = chars[i] == chars[j] && is_pal[i + 1][j - 1];
}
}
let mut dp = vec![0; n];
for i in 0..n {
if is_pal[0][i] {
dp[i] = 0;
} else {
dp[i] = i;
for j in 1..=i {
if is_pal[j][i] {
dp[i] = dp[i].min(dp[j - 1] + 1);
}
}
}
}
dp[n - 1]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cuts() {
assert_eq!(min_cuts("aab"), 1);
}
#[test]
fn test_palindrome() {
assert_eq!(min_cuts("aa"), 0);
}
}#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cuts() {
assert_eq!(min_cuts("aab"), 1);
}
#[test]
fn test_palindrome() {
assert_eq!(min_cuts("aa"), 0);
}
}
Deep Comparison
OCaml vs Rust: Palindrome Partitioning
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
palindrome_partition_list(s) -> Vec<Vec<String>> that returns all valid palindrome partitions (exponential output), using backtracking on the precomputed is_pal table.minimum_palindrome_partition_k(s, k) — minimum cuts to get exactly k palindrome parts, adding a third dimension to the DP.