ExamplesBy LevelBy TopicLearning Paths
791 Advanced

791-palindrome-partitioning — Palindrome Partitioning

Functional Programming

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

  • • Pre-compute a 2D boolean table is_pal[i][j] for all substring palindrome checks
  • • Use the palindrome expansion technique: is_pal[i][j] = chars[i]==chars[j] && is_pal[i+1][j-1]
  • • Apply minimum cuts DP: dp[i] = min over j: dp[j-1] + 1 when is_pal[j][i]
  • • Understand the O(n²) time complexity for both the palindrome check and the cuts DP
  • • See how this combines interval DP (palindrome table) with linear DP (cuts)
  • 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

  • Two-phase DP: Both languages use the same two-phase approach (palindrome table then cuts); the structure is algorithm-determined, not language-determined.
  • Boundary conditions: Rust's dp[i] = i initialization bounds cuts; OCaml uses the same; careful initialization avoids off-by-one errors.
  • Combination: This problem requires combining two DP patterns — interval DP and linear DP — which is the same in both languages.
  • O(n) space improvement: Manacher's algorithm computes palindrome checks in O(n) time; combined with the cuts DP, achieves O(n) total space.
  • 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);
        }
    }
    ✓ Tests Rust test suite
    #[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

    AspectOCamlRust
    Type systemHindley-MilnerOwnership + traits
    MemoryGCZero-cost abstractions
    MutabilityExplicit refmut keyword
    Error handlingOption/ResultResult<T, E>

    See README.md for detailed comparison.

    Exercises

  • Implement palindrome_partition_list(s) -> Vec<Vec<String>> that returns all valid palindrome partitions (exponential output), using backtracking on the precomputed is_pal table.
  • Use Manacher's algorithm (example 820) to compute the palindrome table in O(n) time, improving the total complexity.
  • Extend to minimum_palindrome_partition_k(s, k) — minimum cuts to get exactly k palindrome parts, adding a third dimension to the DP.
  • Open Source Repos