Skip to content

Commit

Permalink
feat: added palindrome partition dp solution
Browse files Browse the repository at this point in the history
  • Loading branch information
ghimiresdp committed Sep 29, 2024
1 parent 6d1d6c2 commit 1d0433a
Show file tree
Hide file tree
Showing 4 changed files with 191 additions and 9 deletions.
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,14 +95,14 @@ cargo test --bin huffman

### [4.2. Dynamic Programming](problem-solving/dp/)

1. [List group by consecutive numbers](problem-solving/mid/consecutive_groups.rs) `cargo run --bin consecutive_groups`
2. [Find the length of the longest substring with maximum 2 repetition](problem-solving/mid/repeat.rs)`cargo run --bin repeat`
3. [Find the index of 2 numbers in an array whose sum equals to the provided target](problem-solving/mid/two_sum.rs) `cargo run --bin two_sum`
4. [Minimize the Sum from an array](problem-solving/mid/minimize_sum.rs) `cargo run --bin minimize_sum`
5. [Fibonacci Series](./problem-solving/dp/fibonacci.rs) `cargo run --bin fibonacci`
6. [Longest Common Subsequence](./problem-solving/dp/longest_common_subsequence.rs) `cargo run --bin lcs`
7. [Coin Change Problem](./problem-solving/dp/coin_change.rs) `cargo run --bin coin_change`
8. [Palindrome Partition]
1. [List group by consecutive numbers](problem-solving/dp/consecutive_groups.rs) `cargo run --bin consecutive_groups`
2. [Find the length of the longest substring with maximum 2 repetition](problem-solving/dp/repeat.rs)`cargo run --bin repeat`
3. [Find the index of 2 numbers in an array whose sum equals to the provided target](problem-solving/dp/two_sum.rs) `cargo run --bin two_sum`
4. [Minimize the Sum from an array](problem-solving/dp/minimize_sum.rs) `cargo run --bin minimize_sum`
5. [Fibonacci Series](problem-solving/dp/fibonacci.rs) `cargo run --bin fibonacci`
6. [Longest Common Subsequence](problem-solving/dp/longest_common_subsequence.rs) `cargo run --bin lcs`
7. [Coin Change Problem](problem-solving/dp/coin_change.rs) `cargo run --bin coin_change`
8. [Palindrome Partition](problem-solving/dp/palindrome_partition.rs) `cargo run --bin palindrome_partition`

### [4.3. Pro Level Problems](problem-solving/pro/)

Expand Down
4 changes: 4 additions & 0 deletions problem-solving/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,7 @@ path = "dp/longest_common_subsequence.rs"
[[bin]]
name = 'coin_change'
path = 'dp/coin_change.rs'

[[bin]]
name = 'palindrome_partition'
path = 'dp/palindrome_partition.rs'
2 changes: 1 addition & 1 deletion problem-solving/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
5. [Fibonacci Series](dp/fibonacci.rs) `cargo run --bin fibonacci`
6. [Longest Common Subsequence](dp/longest_common_subsequence.rs) `cargo run --bin lcs`
7. [Coin Change Problem](dp/coin_change.rs) `cargo run --bin coin_change`
8. [Palindrome Partition]
8. [Palindrome Partition](dp/palindrome_partition.rs) `cargo run --bin palindrome_partition`

### [4.3. Pro Level Problems](pro/)

Expand Down
178 changes: 178 additions & 0 deletions problem-solving/dp/palindrome_partition.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
use std::cmp::min;

/// a function to check if the given str is palindrome
fn is_palindrome(word: &str) -> bool {
let mut from = 0;
let mut to = word.len() - 1;
while from < to {
if word.chars().nth(from).unwrap() != word.chars().nth(to).unwrap() {
return false;
}
from += 1;
to -= 1;
}
return true;
}
/// recursively check if the given string is palindrome
/// as it is a recursive method, it will overlap many sub-problems hence it is
/// not an optimal solution.
fn min_cuts_recursive(word: &str) -> usize {
if is_palindrome(&word) {
return 0;
} else {
let mut _min = usize::MAX;
for x in 1..word.len() {
_min = min(
_min,
1 + min_cuts_recursive(&word[..x]) + min_cuts_recursive(&word[x..]),
);
}
return _min;
};
}

/// # min_cuts
/// dynamic programming approach
/// this approach reuses the overlapped subproblem so that we do not need to
/// find out the palindrome if it is already found.
fn min_cuts(word: &str) -> usize {
let len = word.len();
if len == 0 {
return 0;
}

// we create a table to store the palindrome test for all values from range
// word[0..=0] to word[len-1..=len-1]
let mut palindrome = vec![vec![false; len]; len];

// all the characters with length 1 will be palindrome
// so word[x..=x] is always palindrome.
for i in 0..len {
palindrome[i][i] = true;
}

// now we need to check if the string slices with length larger than 1 to be
// palindrome
// example: DAD might contain DA, AD, DAD
// example: ADAM might contain AD, DA, AM, ADA, DAM, ADAM

// start from 2 upto length of the string
for sub_len in 2..=len {
// extract the slice from the starting of the substring
for i in 0..=(len - sub_len) {
// find the ending position of the substring
// example: for ADAM with sub_len=2 nd i = 1, substring = A [DA] M
let j = i + sub_len - 1;

// given, the smaller substrings being calculated and is palindrome,
// check if adding characters to the first ant last makes it palindrome
// again.
// if starting and ending is same, then the slice is palindrome.
// (precondition: everything inside [] is palindrome)
// i.e. palindrome[i + 1][j - 1] == true
//
// example: D + [A] + D = DAD is palindrome
// example: D + [A] + N = DAN is not palindrome
// where [A] already is palindrome
//
// EXAMPLE: for sub_len == 2, D [] D = DD, is palindrome
// EXAMPLE: for sub_len>2, if D [A] D = DAD, is palindrome
//
// here palindrome[i + 1][j - 1] = [A] = true (since it is palindrome)
// for above case i -> D[A]D <- j, [A] is palindrome
if word.chars().nth(i).unwrap() == word.chars().nth(j).unwrap()
&& (sub_len == 2 || palindrome[i + 1][j - 1])
{
// if above condition passes, the new slice is also palindrome
// where new slice is [CHAR at i] + [OLD_SLICE] + [CHAR at j]
palindrome[i][j] = true;
}
}
}

// now create a vector to store minimum cuts required;
let mut cuts = vec![usize::MAX; len];
// for single characters, cuts = 0
cuts[0] = 0;
for i in 1..len {
// if the word is palindrome from first to ith index, then cuts for ith
// index is 0. i.e. we do not need to cut the palindrome
if palindrome[0][i] {
cuts[i] = 0;
} else {
// if word [0..i] is not palindrome, then we need to cut once (1+..)
// and then check for cuts on both left and right side of ith index.
// i.e. (1 + <left_cuts> + <right_cuts>)
//
// for that, we need to check slices from [j..i] to [0..i]
// if we find palindrome in that range, we update cuts whenever
// we find minimum cuts that satisfies the precondition
// where precondition is palindrome[j][i] == true.
let mut j = i;
while j >= 1 {
if palindrome[j][i] {
if cuts[j - 1] + 1 < cuts[i] {
cuts[i] = cuts[j - 1] + 1;
}
}
j -= 1;
}
}
}
return cuts[len - 1];
}

fn main() {
println!("+ {:-<20} + {:-<20} + {:-<20} + {:-<20} +", "", "", "", "");
println!(
"| {:^20} | {:^20} | {:^20} | {:^20} |",
"word", "is_palindrome", "min_cuts_recursive", "min_cuts_dp"
);
println!("+ {:-<20} + {:-<20} + {:-<20} + {:-<20} +", "", "", "", "");
for word in vec!["dad", "dada", "daad", "abadad", "asadadnan", "abcdefg"] {
println!(
"| {:^20} | {:^20} | {:^20} | {:^20} |",
word,
is_palindrome(word),
min_cuts_recursive(word),
min_cuts(word)
);
}
println!("+ {:-<20} + {:-<20} + {:-<20} + {:-<20} +", "", "", "", "");
// output
// + -------------------- + -------------------- + -------------------- + -------------------- +
// | word | is_palindrome | min_cuts_recursive | min_cuts_dp |
// + -------------------- + -------------------- + -------------------- + -------------------- +
// | dad | true | 0 | 0 |
// | dada | false | 1 | 1 |
// | daad | true | 0 | 0 |
// | abadad | false | 1 | 1 |
// | asadadnan | false | 2 | 2 |
// | abcdefg | false | 6 | 6 |
// + -------------------- + -------------------- + -------------------- + -------------------- +
}

#[cfg(test)]
mod tests {
use crate::{is_palindrome, min_cuts, min_cuts_recursive};

#[test]
fn test_palindrome() {
assert!(!is_palindrome("AqwwqB"));
assert!(is_palindrome("dad"));
}

#[test]
fn test_cuts_recursive() {
assert_eq!(min_cuts_recursive("AqwwqB"), 2);
assert_eq!(min_cuts_recursive("daddydda"), 1);
assert_eq!(min_cuts_recursive("abcde"), 4);
}

#[test]
fn test_cuts() {
assert_eq!(min_cuts("AqwwqB"), 2);
assert_eq!(min_cuts("daddydda"), 1);
assert_eq!(min_cuts("abcde"), 4);
}
}

0 comments on commit 1d0433a

Please sign in to comment.