๐Ÿฆ€ Functional Rust

1055: Longest Common Subsequence

Difficulty: Intermediate Category: Dynamic Programming Concept: Find the longest subsequence common to two strings using 2D DP table with backtracking for reconstruction Key Insight: The DP table stores lengths, but the actual subsequence is recovered by backtracking from `dp[m][n]` โ€” following diagonal moves (match) vs horizontal/vertical moves (skip).
// 1055: Longest Common Subsequence โ€” 2D DP + Backtrack

use std::collections::HashMap;

// Approach 1: 2D DP table for length
fn lcs_length(s1: &str, s2: &str) -> usize {
    let (a, b): (Vec<char>, Vec<char>) = (s1.chars().collect(), s2.chars().collect());
    let (m, n) = (a.len(), b.len());
    let mut dp = vec![vec![0usize; n + 1]; m + 1];
    for i in 1..=m {
        for j in 1..=n {
            dp[i][j] = if a[i - 1] == b[j - 1] {
                dp[i - 1][j - 1] + 1
            } else {
                dp[i - 1][j].max(dp[i][j - 1])
            };
        }
    }
    dp[m][n]
}

// Approach 2: DP + backtrack to reconstruct
fn lcs_string(s1: &str, s2: &str) -> String {
    let (a, b): (Vec<char>, Vec<char>) = (s1.chars().collect(), s2.chars().collect());
    let (m, n) = (a.len(), b.len());
    let mut dp = vec![vec![0usize; n + 1]; m + 1];
    for i in 1..=m {
        for j in 1..=n {
            dp[i][j] = if a[i - 1] == b[j - 1] {
                dp[i - 1][j - 1] + 1
            } else {
                dp[i - 1][j].max(dp[i][j - 1])
            };
        }
    }
    // Backtrack
    let mut result = Vec::new();
    let (mut i, mut j) = (m, n);
    while i > 0 && j > 0 {
        if a[i - 1] == b[j - 1] {
            result.push(a[i - 1]);
            i -= 1;
            j -= 1;
        } else if dp[i - 1][j] > dp[i][j - 1] {
            i -= 1;
        } else {
            j -= 1;
        }
    }
    result.reverse();
    result.into_iter().collect()
}

// Approach 3: Recursive with memoization
fn lcs_memo(s1: &str, s2: &str) -> usize {
    let (a, b): (Vec<char>, Vec<char>) = (s1.chars().collect(), s2.chars().collect());
    fn solve(i: usize, j: usize, a: &[char], b: &[char], cache: &mut HashMap<(usize, usize), usize>) -> usize {
        if i == 0 || j == 0 { return 0; }
        if let Some(&v) = cache.get(&(i, j)) { return v; }
        let v = if a[i - 1] == b[j - 1] {
            solve(i - 1, j - 1, a, b, cache) + 1
        } else {
            solve(i - 1, j, a, b, cache).max(solve(i, j - 1, a, b, cache))
        };
        cache.insert((i, j), v);
        v
    }
    let mut cache = HashMap::new();
    solve(a.len(), b.len(), &a, &b, &mut cache)
}

fn main() {
    println!("LCS(\"abcde\", \"ace\") = {} (\"{}\")", lcs_length("abcde", "ace"), lcs_string("abcde", "ace"));
    println!("LCS(\"AGGTAB\", \"GXTXAYB\") = \"{}\"", lcs_string("AGGTAB", "GXTXAYB"));
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_lcs_length() {
        assert_eq!(lcs_length("abcde", "ace"), 3);
        assert_eq!(lcs_length("abc", "abc"), 3);
        assert_eq!(lcs_length("abc", "def"), 0);
    }

    #[test]
    fn test_lcs_string() {
        assert_eq!(lcs_string("abcde", "ace"), "ace");
        assert_eq!(lcs_string("AGGTAB", "GXTXAYB"), "GTAB");
    }

    #[test]
    fn test_lcs_memo() {
        assert_eq!(lcs_memo("abcde", "ace"), 3);
        assert_eq!(lcs_memo("AGGTAB", "GXTXAYB"), 4);
    }
}
(* 1055: Longest Common Subsequence โ€” 2D DP + Backtrack *)

(* Approach 1: 2D DP table for length *)
let lcs_length s1 s2 =
  let m = String.length s1 and n = String.length s2 in
  let dp = Array.init (m + 1) (fun _ -> Array.make (n + 1) 0) in
  for i = 1 to m do
    for j = 1 to n do
      if s1.[i - 1] = s2.[j - 1] then
        dp.(i).(j) <- dp.(i - 1).(j - 1) + 1
      else
        dp.(i).(j) <- max dp.(i - 1).(j) dp.(i).(j - 1)
    done
  done;
  dp.(m).(n)

(* Approach 2: DP + backtrack to reconstruct the subsequence *)
let lcs_string s1 s2 =
  let m = String.length s1 and n = String.length s2 in
  let dp = Array.init (m + 1) (fun _ -> Array.make (n + 1) 0) in
  for i = 1 to m do
    for j = 1 to n do
      if s1.[i - 1] = s2.[j - 1] then
        dp.(i).(j) <- dp.(i - 1).(j - 1) + 1
      else
        dp.(i).(j) <- max dp.(i - 1).(j) dp.(i).(j - 1)
    done
  done;
  (* Backtrack to find the actual subsequence *)
  let buf = Buffer.create 16 in
  let i = ref m and j = ref n in
  while !i > 0 && !j > 0 do
    if s1.[!i - 1] = s2.[!j - 1] then begin
      Buffer.add_char buf s1.[!i - 1];
      decr i; decr j
    end else if dp.(!i - 1).(!j) > dp.(!i).(!j - 1) then
      decr i
    else
      decr j
  done;
  let s = Buffer.contents buf in
  String.init (String.length s) (fun i -> s.[String.length s - 1 - i])

(* Approach 3: Recursive with memoization *)
let lcs_memo s1 s2 =
  let cache = Hashtbl.create 128 in
  let rec solve i j =
    if i = 0 || j = 0 then 0
    else
      match Hashtbl.find_opt cache (i, j) with
      | Some v -> v
      | None ->
        let v =
          if s1.[i - 1] = s2.[j - 1] then solve (i - 1) (j - 1) + 1
          else max (solve (i - 1) j) (solve i (j - 1))
        in
        Hashtbl.add cache (i, j) v;
        v
  in
  solve (String.length s1) (String.length s2)

let () =
  assert (lcs_length "abcde" "ace" = 3);
  assert (lcs_length "abc" "abc" = 3);
  assert (lcs_length "abc" "def" = 0);
  assert (lcs_string "abcde" "ace" = "ace");
  assert (lcs_string "AGGTAB" "GXTXAYB" = "GTAB");
  assert (lcs_memo "abcde" "ace" = 3);
  assert (lcs_memo "AGGTAB" "GXTXAYB" = 4);
  Printf.printf "โœ“ All tests passed\n"

๐Ÿ“Š Detailed Comparison

Longest Common Subsequence โ€” Comparison

Core Insight

LCS uses a 2D DP table where `dp[i][j]` = length of LCS of first `i` chars of s1 and first `j` chars of s2. Backtracking from the bottom-right corner reconstructs the actual subsequence.

OCaml Approach

  • `Array.init` for 2D table, `Buffer` for string reconstruction
  • Character access via `s.[i]` (O(1) for ASCII strings)
  • Backtracking uses `ref` cells for mutable indices
  • `String.init` with index reversal to flip the backtracked result

Rust Approach

  • Strings converted to `Vec<char>` first (UTF-8 aware)
  • `vec![vec![0; n+1]; m+1]` for 2D table
  • Backtracking pushes to `Vec<char>` then `reverse()`
  • `into_iter().collect()` to convert back to `String`

Comparison Table

AspectOCamlRust
String indexing`s.[i]` โ€” O(1) byte accessMust convert to `Vec<char>` first
2D table`Array.init` + `Array.make``vec![vec![]; ...]`
String building`Buffer.add_char` + `Buffer.contents``Vec<char>` + `.collect()`
Reversal`String.init` with reversed index`.reverse()` in-place
UTF-8 handlingByte-level (ASCII only)Full Unicode via `chars()`