Rust for Algorithmic Problem-Solving
Greedy Algorithms
A greedy algorithm builds a solution step by step, making the locally optimal choice at each decision point without backtracking. When a problem exhibits the greedy-choice property and optimal substructure, sorting the input (sort_unstable_by_key) or maintaining a priority queue (BinaryHeap) yields the globally optimal answer in $O(n \log n)$ time.
Classic Greedy Sorting Strategies:
1. Interval Scheduling (Max Non-Overlapping Events):
---> Sort intervals by END time ascending (`|iv| iv.end`)!
2. Interval Merging / Sweep-Line (Overlapping Windows):
---> Sort intervals by START time ascending (`|iv| iv.start`)!
3. Deadline / Regret Minimization (Course Schedule / Job Sequencing):
---> Sort by deadline ascending + evict longest-duration taken job via Max-Heap!
1. Maximum Non-Overlapping Intervals (Earliest End Time First)
To select the maximum number of mutually compatible intervals [start, end) from a set of $N$ intervals:
- Sort by
endtime ascending (intervals.sort_unstable_by_key(|iv| iv.1)). - Greedily pick every interval whose
start >= last_end:
/// Selects the maximum number of non-overlapping half-open intervals `[start, end)`
/// in O(n log n) time by sorting by earliest end time.
pub fn max_disjoint_intervals(mut intervals: Vec<(i64, i64)>) -> Vec<(i64, i64)> {
intervals.sort_unstable_by_key(|&(_start, end)| end);
let mut chosen = Vec::new();
let mut last_end = i64::MIN;
for (start, end) in intervals {
if start >= last_end {
chosen.push((start, end));
last_end = end;
}
}
chosen
}
2. Merging Overlapping Intervals & Sweep-Line Peak Concurrency
Sorting intervals by start time enables merging overlapping intervals in a single pass, while sorting (timestamp, +1 / -1) event points (Sweep-Line algorithm) computes the maximum number of simultaneously active intervals (such as minimum conference rooms or peak concurrent network connections):
/// Merges all overlapping or touching intervals `[start, end]` in O(n log n) time.
pub fn merge_intervals(mut intervals: Vec<(i64, i64)>) -> Vec<(i64, i64)> {
if intervals.is_empty() {
return Vec::new();
}
intervals.sort_unstable();
let mut merged = Vec::with_capacity(intervals.len());
let mut current = intervals[0];
for &(start, end) in &intervals[1..] {
if start <= current.1 {
// Overlaps with `current`; extend the right endpoint:
current.1 = current.1.max(end);
} else {
merged.push(current);
current = (start, end);
}
}
merged.push(current);
merged
}
/// Computes the peak number of simultaneously active half-open intervals `[start, end)`
/// using an O(n log n) event sweep-line.
pub fn peak_concurrent_intervals(intervals: &[(i64, i64)]) -> usize {
let mut events = Vec::with_capacity(intervals.len() * 2);
for &(start, end) in intervals {
// Place `-1` (end event) BEFORE `+1` (start event) at the exact same timestamp `t`
// so half-open intervals `[10, 20)` and `[20, 30)` do not count as overlapping at 20!
events.push((start, 1isize));
events.push((end, -1isize));
}
events.sort_unstable();
let mut active = 0isize;
let mut peak = 0isize;
for (_, delta) in events {
active += delta;
peak = peak.max(active);
}
peak as usize
}
fn main() {
let raw = vec![(1, 4), (2, 3), (3, 6), (7, 9)];
let disjoint = max_disjoint_intervals(raw.clone());
assert_eq!(disjoint, vec![(2, 3), (3, 6), (7, 9)]);
let merged = merge_intervals(raw.clone());
assert_eq!(merged, vec![(1, 6), (7, 9)]);
let peak = peak_concurrent_intervals(&raw);
assert_eq!(peak, 2);
println!("Max disjoint: {disjoint:?}, Merged: {merged:?}, Peak concurrent: {peak}");
}
Max disjoint: [(2, 3), (3, 6), (7, 9)], Merged: [(1, 6), (7, 9)], Peak concurrent: 2