|
| 1 | +//! # LeetCode Problem: 1910 - Remove All Occurrences of a Substring |
| 2 | +//! |
| 3 | +//! Difficulty: Medium |
| 4 | +//! |
| 5 | +//! Link: https://leetcode.com/problems/remove-all-occurrences-of-a-substring/ |
| 6 | +//! |
| 7 | +//! ### Complexity Analysis |
| 8 | +//! |
| 9 | +//! Stack-based solution: |
| 10 | +//! |
| 11 | +//! - Time Complexity: O(n * m) - We traverse the string n times and remove part m times. |
| 12 | +//! - Space Complexity: O(n) - The extra space required depends on the number of items |
| 13 | +//! stored in the stack. |
| 14 | +//! |
| 15 | +//! According to the benchmark (`cargo bench --bench p1910`), |
| 16 | +//! in some cases, the stack-based solution demonstrates superior performance |
| 17 | +//! compared to the direct string manipulation approach. |
| 18 | +//! Also, LeetCode claims that the stack-based solution is more memory-efficient. |
| 19 | +//! Execute `. ./profile.sh p1910_memory 1 2` to run the memory benchmark locally and probably |
| 20 | +//! see some different results. |
| 21 | +pub struct Solution; |
| 22 | + |
| 23 | +impl Solution { |
| 24 | + pub fn remove_occurrences(s: String, part: String) -> String { |
| 25 | + let mut result = s; |
| 26 | + |
| 27 | + // Continue removing part from result until it no longer exists |
| 28 | + while let Some(pos) = result.find(&part) { |
| 29 | + // Remove the part at position pos |
| 30 | + result = result[..pos].to_string() + &result[pos + part.len()..]; |
| 31 | + } |
| 32 | + |
| 33 | + result |
| 34 | + } |
| 35 | + |
| 36 | + pub fn remove_occurrences_stack(s: String, part: String) -> String { |
| 37 | + let mut stack: Vec<char> = Vec::new(); |
| 38 | + let mut part_chars: Vec<char> = part.chars().collect(); |
| 39 | + let last = part_chars.pop().unwrap(); // Get the last character of the part |
| 40 | + let length = part_chars.len(); |
| 41 | + |
| 42 | + for c in s.chars() { |
| 43 | + if c != last { |
| 44 | + // If current character is not the last character of part, push to stack |
| 45 | + stack.push(c); |
| 46 | + } else { |
| 47 | + // If we have enough characters in the stack and they match part_chars |
| 48 | + if stack.len() >= length { |
| 49 | + let start_idx = stack.len() - length; |
| 50 | + let matches = stack[start_idx..] |
| 51 | + .iter() |
| 52 | + .zip(part_chars.iter()) |
| 53 | + .all(|(&a, &b)| a == b); |
| 54 | + |
| 55 | + if matches { |
| 56 | + // Pop the matching prefix from the stack |
| 57 | + for _ in 0..length { |
| 58 | + stack.pop(); |
| 59 | + } |
| 60 | + } else { |
| 61 | + // Not a match, push the current character |
| 62 | + stack.push(c); |
| 63 | + } |
| 64 | + } else { |
| 65 | + // Stack doesn't have enough characters, push current |
| 66 | + stack.push(c); |
| 67 | + } |
| 68 | + } |
| 69 | + } |
| 70 | + |
| 71 | + // Convert the stack back to a string |
| 72 | + stack.iter().collect() |
| 73 | + } |
| 74 | +} |
| 75 | + |
| 76 | +#[cfg(test)] |
| 77 | +mod tests { |
| 78 | + use super::Solution; |
| 79 | + |
| 80 | + #[test] |
| 81 | + fn test_remove_occurrences() { |
| 82 | + let test_cases = [ |
| 83 | + ("daabcbaabcbc", "abc", "dab"), |
| 84 | + ("axxxxyyyyb", "xy", "ab"), |
| 85 | + ("gjzgbpggjzgbpgsvpwdk", "gjzgbpg", "svpwdk"), |
| 86 | + ( |
| 87 | + "wwwwwwwwwwwwwwwwwwwwwvwwwwswxwwwwsdwxweeohapwwzwuwajrnogb", |
| 88 | + "w", |
| 89 | + "vsxsdxeeohapzuajrnogb", |
| 90 | + ), |
| 91 | + ]; |
| 92 | + for (idx, (s, part, expected)) in test_cases.iter().enumerate() { |
| 93 | + let result = Solution::remove_occurrences(s.to_string(), part.to_string()); |
| 94 | + assert_eq!( |
| 95 | + result, *expected, |
| 96 | + "Test case #{idx}: with s={s:?}, part={part:?}, expected {expected:?}, got {result:?}" |
| 97 | + ); |
| 98 | + let result = Solution::remove_occurrences_stack(s.to_string(), part.to_string()); |
| 99 | + assert_eq!( |
| 100 | + result, *expected, |
| 101 | + "Test case #{idx}: with s={s:?}, part={part:?}, expected {expected:?}, got {result:?}" |
| 102 | + ); |
| 103 | + } |
| 104 | + } |
| 105 | +} |
0 commit comments