Skip to content

Commit 54372ff

Browse files
committed
Added problems: 1910 - Remove All Occurrences of a Substring + Memory profiling with visualization
1 parent 72e58a9 commit 54372ff

File tree

9 files changed

+276
-2
lines changed

9 files changed

+276
-2
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# will have compiled files and executables
33
debug/
44
target/
5+
tmp/
56

67
# These are backup files generated by rustfmt
78
**/*.rs.bk

Cargo.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,11 @@ criterion = "0.7"
1010
[[bench]]
1111
name = "p1814"
1212
harness = false
13+
14+
[[bench]]
15+
name = "p1910"
16+
harness = false
17+
18+
[[bench]]
19+
name = "p1910_memory"
20+
harness = false

README.md

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ Note that this license applies only to my solution code and not to the LeetCode
6262
| 🟠 1814. [Count Nice Pairs in an Array](https://leetcode.com/problems/count-nice-pairs-in-an-array/) | [🦀](src/problems/p1814_count_nice_pairs_in_an_array.rs) | HashMap |
6363
| 🔴 1866. [Number of Ways to Rearrange Sticks With K Sticks Visible](https://leetcode.com/problems/number-of-ways-to-rearrange-sticks-with-k-sticks-visible/) | [🦀](src/problems/p1866_number_of_ways_to_rearrange_sticks_with_k_sticks_visible.rs) | Dynamic Programming |
6464
| 🟢 1876. [Substrings of Size Three with Distinct Characters](https://leetcode.com/problems/substrings-of-size-three-with-distinct-characters/) | [🦀](src/problems/p1876_substrings_of_size_three_with_distinct_characters.rs) | |
65+
| 🟢 1910. [Remove All Occurrences of a Substring](https://leetcode.com/problems/remove-all-occurrences-of-a-substring/) | [🦀](src/problems/p1910_remove_all_occurrences_of_a_substring.rs) | Stack |
6566
| 🟢 1974. [Minimum Time to Type Word Using Special Typewriter](https://leetcode.com/problems/minimum-time-to-type-word-using-special-typewriter/) | [🦀](src/problems/p1974_minimimum_time_to_type_word_using_special_typewriter.rs) | |
6667
| 🟢 2099. [Find Subsequence of Length K With the Largest Sum](https://leetcode.com/problems/find-subsequence-of-length-k-with-the-largest-sum/) | [🦀](src/problems/p2099_find_subsequence_of_length_k_with_the_largest_sum.rs) | |
6768
| 🟠 2201. [Count Artifacts That Can Be Extracted](https://leetcode.com/problems/count-artifacts-that-can-be-extracted/) | [🦀](src/problems/p2201_count_artifacts_that_can_be_extracted.rs) | |
@@ -89,6 +90,13 @@ After cloning the repository, run this command to set up Git hooks:
8990
git config core.hooksPath .githooks
9091
```
9192

93+
If you want to run memory benchmarks, you need to install `valgrind` and `massif-visualizer` on your system.
94+
On Ubuntu, you can do this with:
95+
96+
```bash
97+
sudo apt install valgrind massif-visualizer
98+
```
99+
92100
## How to add a new problem
93101

94102
1. Create a new file in `src/problems/` directory with the name of the problem in snake_case with format
@@ -146,7 +154,7 @@ pub mod pNNNN_problem_title;
146154

147155
For benchmarking, the `criterion` crate is used.
148156

149-
To add a new benchmark, create a new file in the [/benches/](/benches/) directory and add a new `[[branch]]` section
157+
To add a new benchmark, create a new file in the [./benches/](./benches/) directory and add a new `[[branch]]` section
150158
in [Cargo.toml](Cargo.toml).
151159

152160
To execute a benchmark, run one of the following commands:
@@ -171,3 +179,35 @@ cargo bench --bench p1814 "loop_based_.*"
171179

172180
See the [Criterion documentation](https://bheisler.github.io/criterion.rs/book/user_guide/known_limitations.html)
173181
for more information or run `cargo help bench`.
182+
183+
## Memory profiling
184+
185+
For memory profiling, create a new file in the [/benches/](/benches/) directory and use `IMPL` environment variable to
186+
specify implementations to profile. See [./benches/p1910_memory.rs](./benches/p1910_memory.rs) for an example.
187+
Add a new `[[bench]]` section in [Cargo.toml](Cargo.toml) to define the benchmark.
188+
189+
To run a memory benchmark, execute the following command:
190+
191+
```bash
192+
. ./profile.sh <benchmark_name> <implementation_ids>
193+
```
194+
195+
The script accepts the following parameters:
196+
197+
- `<benchmark_name>`: Name of the benchmark file without the `.rs` extension (e.g., `p1910_memory`)
198+
- `<implementation_ids>`: Space-separated list of implementation IDs to profile (e.g., `1 2 3`)
199+
200+
### Example usage
201+
202+
To compare memory usage between implementation 1 and 2 of the `p1910_memory` benchmark:
203+
204+
```bash
205+
. ./profile.sh p1910_memory 1 2
206+
```
207+
208+
This will:
209+
210+
1. Run each implementation through Valgrind's Massif tool
211+
2. Generate memory profile data in the `./tmp/` directory
212+
3. Create output files like and `p1910_memory_massif_1.out``p1910_memory_profile_1.txt`
213+
4. Visualize the profiles using massif-visualizer: `massif-visualizer tmp/p1910_memory_massif_1.out`

benches/p1910.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// benches/p1910
2+
use criterion::{criterion_group, criterion_main, Criterion};
3+
use leetcode_rust::problems::p1910_remove_all_occurrences_of_a_substring::Solution;
4+
use std::hint::black_box;
5+
6+
fn bench_remove_occurrences(c: &mut Criterion) {
7+
let s = "daabcbaabcbcabchwswhabcafdwecabcabcabcwddnewwndewndjwcabcabcabcwddnewwndewndcabcabcabcwddnewwndewndjwenjdewjwenjdewenjdewnhdwehqhufebrhfberkwbfhkewnjkewnhjkfqfkwrjijif3fednavjkbhewkqfndwjebnfhkqwbfhkebwhqfbejqewbfhcdjhgfyqwkncjerlqnwqfukerbcabcabcabcwddnewwndewndjwenjdewygfhwuejdwqebferwhfqhyieruwhfnewjldnukbgrfekndjkwqengktrnfwkcabcabcabcwcabcabcabcwddnewwndewndjwenjdewddnewwndewndjwenjdewejdjlw;fkr9pewpquaavhbndlkmcdkj.dsfsbcnjwenvkhceasjncjkewrnvkhjecabcabcabcwddnewwcabcabcabcwddnewwncabcabcabcwddnewwndewndjwcabcabcabcwddnewwndewndjwenjdewenjdewdewndjwenjdewndewndjwenjdewrbvhkerbvhkbenrjkvneraw.vnerjkbvjkervnjerdbacabcabcfewabcabcabcabcacabfercabcabcabcabcbacaafweewcbabcabcabacbcabcabcabcababcbacbabcabcbcacabcbabcabcabcbabcaba".to_string();
8+
let part = "cabcabcabcwddnewwndewndjwenjdew".to_string();
9+
10+
c.bench_function("remove_occurrences", |b| {
11+
b.iter(|| Solution::remove_occurrences(black_box(s.clone()), black_box(part.clone())))
12+
});
13+
14+
c.bench_function("remove_occurrences_stack", |b| {
15+
b.iter(|| Solution::remove_occurrences_stack(black_box(s.clone()), black_box(part.clone())))
16+
});
17+
}
18+
19+
criterion_group!(benches, bench_remove_occurrences);
20+
criterion_main!(benches);

benches/p1910_memory.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// benches/p1910_memory.rs
2+
use criterion::{criterion_group, criterion_main, Criterion};
3+
use leetcode_rust::problems::p1910_remove_all_occurrences_of_a_substring::Solution;
4+
use std::hint::black_box;
5+
6+
fn bench_memory_usage(c: &mut Criterion) {
7+
// Get implementation choice from environment (1=standard, 2=stack)
8+
let impl_choice = std::env::var("IMPL").unwrap_or_else(|_| "1".to_string());
9+
10+
// Larger input for more noticeable memory patterns
11+
let base_s = "daabcbaabcbcabchwswhabcafdwecabcabcabcwddnewwndewndjwcabcabcabcwddnewwndewndcabcabcabcwddnewwndewndjwenjdewjwenjdewenjdewnhdwehqhufebrhfberkwbfhkewnjkewnhjkfqfkwrjijif3fednavjkbhewkqfndwjebnfhkqwbfhkebwhqfbejqewbfhcdjhgfyqwkncjerlqnwqfukerbcabcabcabcwddnewwndewndjwenjdewygfhwuejdwqebferwhfqhyieruwhfnewjldnukbgrfekndjkwqengktrnfwkcabcabcabcwcabcabcabcwddnewwndewndjwenjdewddnewwndewndjwenjdewejdjlw;fkr9pewpquaavhbndlkmcdkj.dsfsbcnjwenvkhceasjncjkewrnvkhjecabcabcabcwddnewwcabcabcabcwddnewwncabcabcabcwddnewwndewndjwcabcabcabcwddnewwndewndjwenjdewenjdewdewndjwenjdewndewndjwenjdewrbvhkerbvhkbenrjkvneraw.vnerjkbvjkervnjerdbacabcabcfewabcabcabcabcacabfercabcabcabcabcbacaafweewcbabcabcabacbcabcabcabcababcbacbabcabcbcacabcbabcabcabcbabcaba";
12+
let part = "cabcabcabcwddnewwndewndjwenjdew";
13+
14+
// Repeat string to make memory usage more pronounced
15+
let repeat = 1000;
16+
let s = base_s.repeat(repeat);
17+
let part = part.to_string();
18+
19+
println!("Testing with string length: {}", s.len());
20+
21+
if impl_choice == "1" {
22+
println!("Testing standard implementation");
23+
c.bench_function("remove_occurrences_memory", |b| {
24+
b.iter(|| Solution::remove_occurrences(black_box(s.clone()), black_box(part.clone())))
25+
});
26+
} else {
27+
println!("Testing stack implementation");
28+
c.bench_function("remove_occurrences_stack_memory", |b| {
29+
b.iter(|| {
30+
Solution::remove_occurrences_stack(black_box(s.clone()), black_box(part.clone()))
31+
})
32+
});
33+
}
34+
}
35+
36+
criterion_group!(benches, bench_memory_usage);
37+
criterion_main!(benches);

profile.sh

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#!/bin/bash
2+
3+
BASE_PATH="./tmp"
4+
5+
# Check if required arguments are provided
6+
if [ $# -lt 2 ]; then
7+
echo "Usage: $0 BENCHMARK_NAME IMPL_VALUES..."
8+
echo "Example: $0 p1910_memory 1 2 3"
9+
exit 1
10+
fi
11+
12+
# Parse arguments
13+
BENCHMARK_NAME=$1
14+
shift # Remove the first argument (BENCHMARK_NAME)
15+
IMPL_VALUES=("$@") # Store all remaining arguments as array
16+
17+
# Ensure the tmp directory exists
18+
mkdir -p "$BASE_PATH"
19+
20+
# Ensure the benchmark is built
21+
echo "Building benchmark: $BENCHMARK_NAME"
22+
cargo build --release --bench $BENCHMARK_NAME
23+
24+
# Find the actual binary - the newest one that matches the pattern
25+
# shellcheck disable=SC2038
26+
MEM_BINARY=$(find target/release/deps -name "$BENCHMARK_NAME-*" -type f -executable | xargs ls -t | head -1)
27+
28+
if [ -z "$MEM_BINARY" ]; then
29+
echo "Error: Could not find the benchmark binary for $BENCHMARK_NAME"
30+
exit 1
31+
fi
32+
33+
echo "Using binary: $MEM_BINARY"
34+
35+
# Run benchmarks for each implementation
36+
for IMPL in "${IMPL_VALUES[@]}"; do
37+
echo "Running implementation $IMPL memory benchmark..."
38+
# shellcheck disable=SC2098
39+
# shellcheck disable=SC2097
40+
IMPL=$IMPL valgrind --tool=massif --massif-out-file="$BASE_PATH/${BENCHMARK_NAME}_massif_${IMPL}.out" \
41+
--detailed-freq=1 --max-snapshots=100 --time-unit=B $MEM_BINARY
42+
43+
echo "Generating report for implementation $IMPL..."
44+
ms_print "$BASE_PATH/${BENCHMARK_NAME}_massif_${IMPL}.out" > "$BASE_PATH/${BENCHMARK_NAME}_profile_${IMPL}.txt"
45+
done
46+
47+
echo "Memory profiling complete!"
48+
echo "Reports saved to:"
49+
for IMPL in "${IMPL_VALUES[@]}"; do
50+
echo " $BASE_PATH/${BENCHMARK_NAME}_profile_${IMPL}.txt"
51+
done
52+
53+
# Construct massif-visualizer command with all output files
54+
VISUALIZER_CMD="massif-visualizer"
55+
for IMPL in "${IMPL_VALUES[@]}"; do
56+
VISUALIZER_CMD+=" $BASE_PATH/${BENCHMARK_NAME}_massif_${IMPL}.out"
57+
done
58+
59+
# Launch massif-visualizer with all output files for comparison
60+
echo "Launching massif-visualizer for comparing implementations..."
61+
echo "Running: $VISUALIZER_CMD"
62+
$VISUALIZER_CMD

src/problems/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ pub mod p1784_check_if_binary_string_has_at_most_one_segment_of_ones;
2828
pub mod p1814_count_nice_pairs_in_an_array;
2929
pub mod p1866_number_of_ways_to_rearrange_sticks_with_k_sticks_visible;
3030
pub mod p1876_substrings_of_size_three_with_distinct_characters;
31+
pub mod p1910_remove_all_occurrences_of_a_substring;
3132
pub mod p1974_minimimum_time_to_type_word_using_special_typewriter;
3233
pub mod p2099_find_subsequence_of_length_k_with_the_largest_sum;
3334
pub mod p2201_count_artifacts_that_can_be_extracted;
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
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+
}

src/problems/p1974_minimimum_time_to_type_word_using_special_typewriter.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ mod tests {
2828
use super::Solution;
2929

3030
#[test]
31-
fn min_time_to_type() {
31+
fn test_min_time_to_type() {
3232
let test_cases = [("abc", 5), ("bza", 7), ("zjpc", 34)];
3333
for (idx, (input, expected)) in test_cases.iter().enumerate() {
3434
let result = Solution::min_time_to_type(input.to_string());

0 commit comments

Comments
 (0)