๐Ÿฆ€ Functional Rust

1061: Word Break

Difficulty: Intermediate Category: Dynamic Programming Concept: Determine if a string can be segmented into dictionary words using boolean DP with HashSet lookup Key Insight: `dp[i] = true` if there exists some `j < i` where `dp[j]` is true and `s[j..i]` is in the dictionary โ€” each position checks all possible last-word boundaries.
// 1061: Word Break โ€” DP + HashSet

use std::collections::{HashMap, HashSet, VecDeque};

// Approach 1: Bottom-up DP
fn word_break(s: &str, words: &[&str]) -> bool {
    let dict: HashSet<&str> = words.iter().copied().collect();
    let n = s.len();
    let mut dp = vec![false; n + 1];
    dp[0] = true;
    for i in 1..=n {
        for j in 0..i {
            if dp[j] && dict.contains(&s[j..i]) {
                dp[i] = true;
                break;
            }
        }
    }
    dp[n]
}

// Approach 2: Recursive with memoization
fn word_break_memo(s: &str, words: &[&str]) -> bool {
    let dict: HashSet<&str> = words.iter().copied().collect();
    fn solve<'a>(s: &'a str, start: usize, dict: &HashSet<&str>, cache: &mut HashMap<usize, bool>) -> bool {
        if start == s.len() { return true; }
        if let Some(&v) = cache.get(&start) { return v; }
        let mut result = false;
        for end_ in (start + 1)..=s.len() {
            if dict.contains(&s[start..end_]) && solve(s, end_, dict, cache) {
                result = true;
                break;
            }
        }
        cache.insert(start, result);
        result
    }
    let mut cache = HashMap::new();
    solve(s, 0, &dict, &mut cache)
}

// Approach 3: BFS approach
fn word_break_bfs(s: &str, words: &[&str]) -> bool {
    let dict: HashSet<&str> = words.iter().copied().collect();
    let n = s.len();
    let mut visited = vec![false; n + 1];
    let mut queue = VecDeque::new();
    queue.push_back(0);
    visited[0] = true;
    while let Some(start) = queue.pop_front() {
        for end_ in (start + 1)..=n {
            if !visited[end_] && dict.contains(&s[start..end_]) {
                if end_ == n { return true; }
                visited[end_] = true;
                queue.push_back(end_);
            }
        }
    }
    false
}

fn main() {
    println!("word_break(\"leetcode\") = {}", word_break("leetcode", &["leet", "code"]));
    println!("word_break(\"applepenapple\") = {}", word_break("applepenapple", &["apple", "pen"]));
}

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

    #[test]
    fn test_word_break() {
        assert!(word_break("leetcode", &["leet", "code"]));
        assert!(word_break("applepenapple", &["apple", "pen"]));
        assert!(!word_break("catsandog", &["cats", "dog", "sand", "and", "cat"]));
    }

    #[test]
    fn test_word_break_memo() {
        assert!(word_break_memo("leetcode", &["leet", "code"]));
        assert!(!word_break_memo("catsandog", &["cats", "dog", "sand", "and", "cat"]));
    }

    #[test]
    fn test_word_break_bfs() {
        assert!(word_break_bfs("leetcode", &["leet", "code"]));
        assert!(!word_break_bfs("catsandog", &["cats", "dog", "sand", "and", "cat"]));
    }
}
(* 1061: Word Break โ€” DP + HashSet *)

module StringSet = Set.Make(String)

(* Approach 1: Bottom-up DP *)
let word_break s words =
  let dict = List.fold_left (fun acc w -> StringSet.add w acc) StringSet.empty words in
  let n = String.length s in
  let dp = Array.make (n + 1) false in
  dp.(0) <- true;
  for i = 1 to n do
    for j = 0 to i - 1 do
      if dp.(j) && StringSet.mem (String.sub s j (i - j)) dict then
        dp.(i) <- true
    done
  done;
  dp.(n)

(* Approach 2: Recursive with memoization *)
let word_break_memo s words =
  let dict = List.fold_left (fun acc w -> StringSet.add w acc) StringSet.empty words in
  let n = String.length s in
  let cache = Hashtbl.create 32 in
  let rec solve start =
    if start = n then true
    else
      match Hashtbl.find_opt cache start with
      | Some v -> v
      | None ->
        let v = ref false in
        for end_ = start + 1 to n do
          if not !v && StringSet.mem (String.sub s start (end_ - start)) dict then
            v := solve end_
        done;
        Hashtbl.add cache start !v;
        !v
  in
  solve 0

(* Approach 3: BFS approach *)
let word_break_bfs s words =
  let dict = List.fold_left (fun acc w -> StringSet.add w acc) StringSet.empty words in
  let n = String.length s in
  let visited = Array.make (n + 1) false in
  let queue = Queue.create () in
  Queue.push 0 queue;
  visited.(0) <- true;
  while not (Queue.is_empty queue) do
    let start = Queue.pop queue in
    for end_ = start + 1 to n do
      if not visited.(end_) && StringSet.mem (String.sub s start (end_ - start)) dict then begin
        if end_ = n then (Queue.clear queue; visited.(n) <- true)
        else begin
          visited.(end_) <- true;
          Queue.push end_ queue
        end
      end
    done
  done;
  visited.(n)

let () =
  assert (word_break "leetcode" ["leet"; "code"] = true);
  assert (word_break "applepenapple" ["apple"; "pen"] = true);
  assert (word_break "catsandog" ["cats"; "dog"; "sand"; "and"; "cat"] = false);

  assert (word_break_memo "leetcode" ["leet"; "code"] = true);
  assert (word_break_memo "catsandog" ["cats"; "dog"; "sand"; "and"; "cat"] = false);

  assert (word_break_bfs "leetcode" ["leet"; "code"] = true);
  assert (word_break_bfs "catsandog" ["cats"; "dog"; "sand"; "and"; "cat"] = false);

  Printf.printf "โœ“ All tests passed\n"

๐Ÿ“Š Detailed Comparison

Word Break โ€” Comparison

Core Insight

Word break checks if a string can be segmented into valid dictionary words. The DP approach marks reachable positions; BFS treats positions as graph nodes with dictionary words as edges.

OCaml Approach

  • `StringSet` via `Set.Make(String)` functor โ€” ordered set
  • `String.sub s j (i-j)` for substring extraction
  • `Queue` module for BFS
  • Pattern matching on `find_opt` for memoization

Rust Approach

  • `HashSet<&str>` โ€” O(1) average lookup
  • String slicing `&s[j..i]` โ€” zero-copy substring (valid for ASCII)
  • `VecDeque` for BFS
  • Early `break` in inner loops for optimization

Comparison Table

AspectOCamlRust
Dictionary type`Set.Make(String)` (tree)`HashSet<&str>` (hash)
Lookup complexityO(log n)O(1) average
Substring`String.sub s pos len` (allocates)`&s[j..i]` (zero-copy slice)
BFS queue`Queue.t` (mutable)`VecDeque`
Early termination`ref` flag + check`break` statement