Refactor code for trapping rain water problem

main
Luca Lombardo 7 months ago
parent 3108b2e612
commit 92681d39e9

@ -1,7 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": []
}

@ -24,19 +24,16 @@ fn trap(height: Vec<i32>) -> i32 {
let mut res = 0; let mut res = 0;
while l < r { while l < r {
match l_max.cmp(&r_max) { if l_max < r_max {
Ordering::Less => {
l += 1; l += 1;
l_max = std::cmp::max(l_max, height[l]); l_max = std::cmp::max(l_max, height[l]);
res += l_max - height[l]; res += l_max - height[l];
}, } else {
_ => {
r -= 1; r -= 1;
r_max = std::cmp::max(r_max, height[r]); r_max = std::cmp::max(r_max, height[r]);
res += r_max - height[r]; res += r_max - height[r];
} }
} }
}
res res
} }
``` ```

@ -1,36 +1,49 @@
use std::cmp::Ordering; use std::cmp::Ordering;
// Function to calculate the total amount of water that can be trapped
fn trap(height: Vec<i32>) -> i32 { fn trap(height: Vec<i32>) -> i32 {
// base case // If the height vector is empty, return 0
if height.len() == 0 { if height.len() == 0 {
return 0; return 0;
} }
let (mut l, mut r) = (0, height.len() - 1); // left and right pointer // Initialize left and right pointers
let (mut l_max, mut r_max) = (height[l], height[r]); // left and right max height of current pointer let (mut l, mut r) = (0, height.len() - 1);
// Initialize the maximum height from the left and right
let (mut l_max, mut r_max) = (height[l], height[r]);
// Initialize the result
let mut res = 0; let mut res = 0;
while l < r { // loop until left and right pointer meet // Loop until the left pointer is less than the right pointer
match l_max.cmp(&r_max) { // compare left and right max height while l < r {
Ordering::Less => { // if left max height is less than right max height means we can trap water in left side // If the maximum height from the left is less than the maximum height from the right
l += 1; // move left pointer if l_max < r_max {
l_max = std::cmp::max(l_max, height[l]); // update left max height // Move the left pointer to the right
res += l_max - height[l]; // add water l += 1;
}, // Update the maximum height from the left
_ => { // if right max height is less than left max height means we can trap water in right side l_max = std::cmp::max(l_max, height[l]);
r -= 1; // move right pointer // Add the difference between the maximum height from the left and the current height to the result
r_max = std::cmp::max(r_max, height[r]); // update right max height res += l_max - height[l];
res += r_max - height[r]; // add water } else {
} // Move the right pointer to the left
r -= 1;
// Update the maximum height from the right
r_max = std::cmp::max(r_max, height[r]);
// Add the difference between the maximum height from the right and the current height to the result
res += r_max - height[r];
} }
} }
// Return the result
res res
} }
fn main() { fn main() {
// Initialize the height vector
let height = vec![0,1,0,2,1,0,1,3,2,1,2,1]; let height = vec![0,1,0,2,1,0,1,3,2,1,2,1];
// Call the trap function and store the result
let res = trap(height); let res = trap(height);
// Print the result
println!("res = {}", res); println!("res = {}", res);
} }

@ -2,7 +2,7 @@
# Comments on the solution # Comments on the solution
We can use two different approach, that are theorethically equivalent in terms of space complexity, but in the reality the second one is more efficient, as we can see in the leetcode results We can use two different approach, that are asymptotically equivalent in terms of space complexity, but in the reality the second one is more efficient, as we can see in the leetcode results
## First approach ## First approach
@ -24,6 +24,10 @@ fn find_peak_element(nums: Vec<i32>) -> i32 {
In this case we are doing a simple binary search where we are just checking if the next element is bigger then the middle. If it is bigger, we can discard the left part of the array, otherwise we can discard the right part of the array. In this way we are sure that we will find a peak, as we are always moving towards the direction of the peak. The only case where we can have a problem is when we are in the peak, but in this case we will just return the left index, as it will be equal to the right index. In this case we are doing a simple binary search where we are just checking if the next element is bigger then the middle. If it is bigger, we can discard the left part of the array, otherwise we can discard the right part of the array. In this way we are sure that we will find a peak, as we are always moving towards the direction of the peak. The only case where we can have a problem is when we are in the peak, but in this case we will just return the left index, as it will be equal to the right index.
Note that it's important that $\forall i$ `nums[i] != nums[i+1]` because otherwise we can have a problem in the algorithm. This is specified in the problem statement at the end of the description
Also note that `nums[-1] = nums[n] = -∞`. In other words, an element is always considered to be strictly greater than a neighbor that is outside the array.
![](https://i.imgur.com/GBiAC3c.png) ![](https://i.imgur.com/GBiAC3c.png)
### Complexity ### Complexity

@ -0,0 +1,9 @@
[package]
name = "frogs-and-mosquitos"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bst-rs = "0.1.0"

@ -0,0 +1,13 @@
The algorithm is designed to solve a problem involving frogs and mosquitoes. The frogs are located at certain positions and have a certain tongue length. Mosquitoes land at certain positions. If a mosquito lands within the reach of a frog's tongue, the frog will eat the mosquito and its tongue length will increase by the size of the mosquito. If a mosquito lands outside the reach of any frog, it will not be eaten.
The algorithm uses a data structure called a segment tree to efficiently manage the positions and tongue lengths of the frogs. A segment tree is a binary tree used for storing information about segments or intervals, and is especially useful for range query problems.
The algorithm starts by initializing a segment tree with the positions and tongue lengths of the frogs. Each node in the segment tree represents a range of positions, and stores the maximum tongue length of any frog within that range.
When a mosquito lands, the algorithm searches the segment tree to find a frog that can reach the mosquito. This is done by checking if the mosquito's position is within the range of a node and if the maximum tongue length in that node is sufficient to reach the mosquito. If such a node is found, the frog's tongue length is updated and the node's maximum value is also updated.
If no such node is found, the mosquito is added to a set of uneaten mosquitoes.
The algorithm also includes a function to insert a new segment into the segment tree. This function is used to update the tree when a frog's tongue length increases. The function ensures that the tree remains balanced and that the maximum values in each node are correct.
Finally, the algorithm includes a function to check if there is a position within a given range that contains exactly a certain number of segments. This function is not used in the main part of the algorithm, but could be useful for additional queries about the positions and tongue lengths of the frogs.

@ -0,0 +1,273 @@
// Frogs and mosquitoes
use std::collections::HashSet;
#[derive(Debug)]
pub struct SegmentTree {
start: usize,
end: usize,
max: i32,
left: Option<Box<SegmentTree>>,
right: Option<Box<SegmentTree>>,
}
impl SegmentTree {
// Function to initialize a segment tree. The values of the nodes are the values of the array
// Use the function new to create a segment tree from an array
pub fn new(start: usize, end: usize, values: &[i32]) -> Option<Box<SegmentTree>> {
if start > end {
return None;
}
if start == end {
// Leaf node
return Some(Box::new(SegmentTree {
start,
end,
max: values[start],
left: None,
right: None,
}));
}
let mid = start + (end - start) / 2;
let left_child = SegmentTree::new(start, mid, values);
let right_child = SegmentTree::new(mid + 1, end, values);
let max = match (&left_child, &right_child) {
(Some(left), Some(right)) => left.max.max(right.max),
(Some(left), None) => left.max,
(None, Some(right)) => right.max,
(None, None) => 0, // Default value for an empty range
};
Some(Box::new(SegmentTree {
start,
end,
max,
left: left_child,
right: right_child,
}))
}
// Function to initialize a segment tree with only one node with no children
// Max value is 0
pub fn segment_tree_no_child(start: usize, end: usize) -> Option<Box<SegmentTree>> {
// function to initialize a segment tree. it has no children and the max value is 0
if start > end {
return None;
}
Some(Box::new(SegmentTree {
start,
end,
max: 0,
left: None,
right: None,
}))
}
// Function to insert a segment [l, r] in the SegmentTree tree
// This function will buid a tree where each node has either 0 or 2 children
// The max value of each node is the number of segments that overlap in the range of the node
pub fn insert_segment(&mut self, l: usize, r: usize) {
if r < self.start || l > self.end {
return;
}
let mid = (self.start + self.end) / 2;
// If the left and right child are not present and the range [l, r] is completely contained in the node, then we update the current node. This is the crucial part of the algorithm, since it's the only way to update the max value of the node
if self.left.is_none() && self.right.is_none() && l <= self.start && r >= self.end {
self.max += 1;
return;
}
// If the range [l, r] is completely contained in the left child, then we insert the segment in the left child
if r <= mid {
match self.left.as_mut() {
Some(left) => {
left.insert_segment(l, r);
self.max = std::cmp::max(self.max, left.max); // Update the max value of the current node
}
None => {
self.left = Some(Box::new(SegmentTree {
start: self.start,
end: mid,
max: self.max,
left: None,
right: None,
}));
self.left.as_mut().unwrap().insert_segment(l, r); // Insert the segment in the left child
// If the right child is not present, then we create it with max = self.max
if self.right.is_none() {
self.right = Some(Box::new(SegmentTree {
start: mid + 1,
end: self.end,
max: self.max,
left: None,
right: None,
}));
}
// Update the max value of the current node
self.max = std::cmp::max(self.max, self.left.as_ref().unwrap().max);
}
}
}
// If the range [l, r] is completely contained in the right child, then we insert the segment in the right child
else if l > mid {
match self.right.as_mut() {
Some(right) => {
right.insert_segment(l, r);
self.max = std::cmp::max(self.max, right.max); // Update the max value of the current node
}
None => {
self.right = Some(Box::new(SegmentTree {
start: mid + 1,
end: self.end,
max: self.max,
left: None,
right: None,
}));
self.right.as_mut().unwrap().insert_segment(l, r); // Insert the segment in the right child
// If the left child is not present, then we create it with max = self.max
if self.left.is_none() {
self.left = Some(Box::new(SegmentTree {
start: self.start,
end: mid,
max: self.max,
left: None,
right: None,
}));
}
// Update the max value of the current node
self.max = std::cmp::max(self.max, self.right.as_ref().unwrap().max);
}
}
}
// If the range [l, r] is split between the left and right child, then we insert the segment in both the children split by mid
// Same as before, if the left or right child is not present, then we create it with max = self.max
else {
match self.left.as_mut() {
Some(left) => {
left.insert_segment(l, mid);
self.max = std::cmp::max(self.max, left.max);
}
None => {
self.left = Some(Box::new(SegmentTree {
start: self.start,
end: mid,
max: self.max,
left: None,
right: None,
}));
self.left.as_mut().unwrap().insert_segment(l, mid);
if self.right.is_none() {
self.right = Some(Box::new(SegmentTree {
start: mid + 1,
end: self.end,
max: self.max,
left: None,
right: None,
}));
}
self.max = std::cmp::max(self.max, self.left.as_ref().unwrap().max);
}
}
match self.right.as_mut() {
Some(right) => {
right.insert_segment(mid + 1, r);
self.max = std::cmp::max(self.max, right.max);
}
None => {
self.right = Some(Box::new(SegmentTree {
start: mid + 1,
end: self.end,
max: self.max,
left: None,
right: None,
}));
self.right.as_mut().unwrap().insert_segment(mid + 1, r);
if self.left.is_none() {
self.left = Some(Box::new(SegmentTree {
start: self.start,
end: mid,
max: self.max,
left: None,
right: None,
}));
}
self.max = std::cmp::max(self.max, self.right.as_ref().unwrap().max);
}
}
}
}
// Function to check if in the range [i, j] there is a position p (in the range) that contains exactly k segments
pub fn is_there(&self, i: usize, j: usize, k: i32) -> i32 {
if i > self.end || j < self.start {
0
} else {
match (&self.left, &self.right) {
(Some(left), _) | (_, Some(left)) if left.is_there(i, j, k) == 1 => 1,
(None, None) if self.max == k => 1,
_ => 0,
}
}
}
}
pub fn frogs_and_mosquitoes(frogs: &mut Vec<(u32, u32)>, mosquitoes: &mut Vec<(u32, u32)>) {
// Let's maintain the set of not eaten mosquitoes
let mut not_eaten_mosquitoes: HashSet<(u32, u32)> = HashSet::new();
// Also we will maintain the set of segments (ai,bi), where ai is the position of the i-th frog and bi=ai+li, where li is the current length of the tongue of the i-th frog.
let mut segments: HashSet<(u32, u32)> = HashSet::new();
// Let the current mosquito landed in the position x. Let's choose segment (ai,bi) with minimal ai such that bix. If the value aix we found the frog that will eat mosquito. Otherwise the current mosquito will not be eaten and we should add it to our set. If the i-th frog will eat mosquito then it's tongue length will be increased by the size of mosquito and we should update segment (ai,bi). After that we should choose the nearest mosquito to the right the from frog and if it's possible eat that mosquito by the i-th frog (this can be done with lower_bound in C++). Possibly we should eat several mosquitoes, so we should repeat this process several times.
for i in 0..frogs.len() {
segments.insert((frogs[i].0, frogs[i].0 + frogs[i].1));
}
for i in 0..mosquitoes.len() {
let x = mosquitoes[i].0;
let y = mosquitoes[i].1;
let mut flag = false;
for j in 0..frogs.len() {
if frogs[j].0 <= x && x <= frogs[j].0 + frogs[j].1 {
flag = true;
frogs[j].1 += y;
segments.remove(&(frogs[j].0, frogs[j].0 + frogs[j].1));
segments.insert((frogs[j].0, frogs[j].0 + frogs[j].1));
break;
}
}
if !flag {
not_eaten_mosquitoes.insert((x, y));
}
}
// Segments (ai,bi) we can store in segment tree by position ai and value bi. Now to find segment we need we can do binary search by the value of ai and check the maximum bi value on the prefix to be at least x. This will work in O(nlog2n) time. We can improve this solution. Let's go down in segment tree in the following manner: if the maximum value bi in the left subtree of segment tree is at least x then we will go to the left, otherwise we will go to the right.
// Let's build a segment tree with the segments
let mut segments_tree = SegmentTree::new(0, 100000, &vec![0; 100001]).unwrap();
for segment in segments.iter() {
segments_tree.insert_segment(segment.0 as usize, segment.1 as usize);
}
// Now we can iterate over the not eaten mosquitoes and check if there is a frog that can eat them
for mosquito in not_eaten_mosquitoes.iter() {
let x = mosquito.0;
let y = mosquito.1;
if segments_tree.is_there(x as usize, x as usize, x as i32 + y as i32) == 1 {
println!("YES");
} else {
println!("NO");
}
}
// The time complexity of this solution is O(nlog2n) for building the segment tree and O(n) for checking if a mosquito can be eaten by a frog
}

@ -9,10 +9,55 @@ Time spent on the project
[![wakatime](https://wakatime.com/badge/user/a3116382-7adb-43ba-9490-83130c4b22c5/project/c7f14dfa-7dc0-4759-a33e-6542dae0135e.svg)](https://wakatime.com/badge/user/a3116382-7adb-43ba-9490-83130c4b22c5/project/c7f14dfa-7dc0-4759-a33e-6542dae0135e) [![wakatime](https://wakatime.com/badge/user/a3116382-7adb-43ba-9490-83130c4b22c5/project/c7f14dfa-7dc0-4759-a33e-6542dae0135e.svg)](https://wakatime.com/badge/user/a3116382-7adb-43ba-9490-83130c4b22c5/project/c7f14dfa-7dc0-4759-a33e-6542dae0135e)
<!-- | Day | Problems | ## List of problems
| --- | -------- |
| 2023-09-18 | [Leaders in an Array](https://github.com/lukefleed/competitive-programming/tree/main/2023_09_18/learders_in_an_array) <br> [Maximum Subarray](https://github.com/lukefleed/competitive-programming/tree/main/2023_09_18/maximum_subarray) <br> [Missing Number](https://github.com/lukefleed/competitive-programming/tree/main/2023_09_18/missing_number) |
| 2023-09-21 | [Sliding Window Maximum](https://github.com/lukefleed/competitive-programming/tree/main/2023_09_21/sliding-window-maximum) <br> [Trapping Rain Water](https://github.com/lukefleed/competitive-programming/tree/main/2023_09_21/trapping-rain-water) |
| 2023-09-25 | [Find Minimum in a Rotated Sorted Array](https://github.com/lukefleed/competitive-programming/tree/main/2023_09_25/find-minimum-in-rotated-sorted-array) <br> [Find Peak Element](https://github.com/lukefleed/competitive-programming/tree/main/2023_09_25/find-peak-element) <br> [First and Last Position of Element in Sorted Array](https://github.com/lukefleed/competitive-programming/tree/main/2023_09_25/First_and_Last_Position_of_Element_in_Sorted_Array) |
--!>
* [x] [Kadane's Algorithm](https://leetcode.com/problems/maximum-subarray/)
* **Idea:** We want to find the maximum subarray sum in an array of integers. It works by _iterating through the array and keeping track of the maximum sum seen so far and the maximum sum ending at the current index_. At each index, the algorithm compares the current element with the maximum sum ending at the previous index plus the current element. If the current element is greater, then the maximum sum ending at the current index is just the current element. Otherwise, the maximum sum ending at the current index is the sum of the maximum sum ending at the previous index and the current element. The algorithm also keeps track of the maximum sum seen so far, which is the maximum of all the maximum sums ending at each index. The final result is the maximum sum seen so far.
* [x] [Trapping Rain Water](https://leetcode.com/problems/trapping-rain-water/)
* The strategy used to solve this problem is the two-pointer approach. We use _two pointers_, one at the beginning and one at the end of the array. We also keep track of _the maximum height_ of the left and right sides. We then move the pointers towards each other, updating the maximum height as we go. If the maximum height of the left side is less than the maximum height of the right side, we know that we can trap water on the left side. Similarly, if the maximum height of the right side is less than the maximum height of the left side, we know that we can trap water on the right side. We keep adding the trapped water to the result until the pointers meet.
* [x] [Search for a peak in an (unsorted) array](https://leetcode.com/problems/find-peak-element/)
* The idea is to use a modified version of the binary search and observe that in there always will be a peak element (the worst case is a monotonically increasing/decreasing sequence, and in that case the peak will be the last/first element). So we just check if `nums[mid]` is greater than `nums[mid+1]` or not. If it is, we can discard the right part of the array, otherwise we can discard the left part of the array. We will always move towards the direction of the peak. It's important to note that `nums[-1] = nums[n] = -∞` and that `nums[i] != nums[i+1]` for all `i`.
* [ ] [Maximum Path Sum](http://practice.geeksforgeeks.org/problems/maximum-path-sum/1)
* [ ] [Hands-On 1](https://pages.di.unipi.it/rossano/blog/2023/handson12324/)
* [ ] [Frogs and Mosquitoes](http://codeforces.com/problemset/problem/609/B?locale=en)
* [ ] [Check if All the Integers in a Range Are Covered](https://leetcode.com/problems/check-if-all-the-integers-in-a-range-are-covered/)
* [ ] [Longest k-Good Segment](https://codeforces.com/contest/616/problem/D?locale=en)
* [ ] [Continuous Subarray Sum](https://leetcode.com/problems/continuous-subarray-sum/)
* [ ] [Update the array](http://www.spoj.com/problems/UPDATEIT/)
* [ ] [Nested segments](http://codeforces.com/problemset/problem/652/D?locale=en)
* [ ] [Powerful array](http://codeforces.com/contest/86/problem/D?locale=en)
* [ ] [Longest Common Subsequence](https://leetcode.com/problems/longest-common-subsequence/)
* [ ] [Minimum Number of Jumps](https://practice.geeksforgeeks.org/problems/minimum-number-of-jumps/0)
* [ ] [Hands-On 2](https://pages.di.unipi.it/rossano/blog/2023/handson22324/)
* [ ] [Subset Sum](https://practice.geeksforgeeks.org/problems/subset-sum-problem/0)
* [ ] [Longest increasing subsequence](https://practice.geeksforgeeks.org/problems/longest-increasing-subsequence/0)
* [ ] [Longest bitonic Sequence](https://practice.geeksforgeeks.org/problems/longest-bitonic-subsequence/0)
* [ ] [N meetings in one room](http://practice.geeksforgeeks.org/problems/n-meetings-in-one-room/0)
* [ ] [Wilbur and array](http://codeforces.com/problemset/problem/596/B?locale=en)
* [ ] [Woodcutters](http://codeforces.com/problemset/problem/545/C?locale=en)
* [ ] [IsBipartite](http://practice.geeksforgeeks.org/problems/bipartite-graph/1)
* [ ] [Hands-On 3](https://pages.di.unipi.it/rossano/blog/2023/handson32324/)
## Theory and algorithms
* [ ] [Binary Search](https://pages.di.unipi.it/rossano/blog/2023/binarysearch/)
* [ ] Trees: representation, traversals, and Binary Search Tree
* [ ] [Sweep line algorithm](https://pages.di.unipi.it/rossano/blog/2023/sweepline)
* [ ] [Two Pointers Technique](https://www.geeksforgeeks.org/two-pointers-technique/)
* [ ] [Prefix Sum](https://pages.di.unipi.it/rossano/blog/2023/prefixsums)
* [ ] [Fenwick tree](https://pages.di.unipi.it/rossano/blog/2023/fenwick)
* [ ] [Range updates](https://blog.mitrichev.ch/2013/05/fenwick-tree-range-updates.html)
* [ ] [Segment Trees](https://en.wikipedia.org/wiki/Segment_tree)
* [ ] [Lazy Propagation](http://www.geeksforgeeks.org/lazy-propagation-in-segment-tree/)
* [ ] [Mo's Algorithm](https://pages.di.unipi.it/rossano/blog/2023/mosalgorithm)
* [ ] [DP](https://github.com/rossanoventurini/CompetitiveProgramming/blob/master/notes/DynamicProgramming.pdf)
* [ ] [Min cost path](https://pages.di.unipi.it/rossano/competitive/notes/Martin_Gardner_Aha_Insight_DP.pdf)
* [ ] [Greedy](https://jeffe.cs.illinois.edu/teaching/algorithms/book/04-greedy.pdf)
* [ ] [Fractional Knapsack](http://www.geeksforgeeks.org/fractional-knapsack-problem/)
* [ ] [Boxes and Heros](https://codeforces.com/blog/entry/63533)
* [ ] Graph algorithms: BFS and DFS
* [ ] Graph algorithms: Strongly Connected Components and Single-Source Shortest Path
* [ ] Graph algorithms: Minimum Spanning Tree (and Disjoint Sets data structures)

Loading…
Cancel
Save