Comprehensive theory, algorithmic patterns, templates, and problem catalog for Greedy Algorithms.
A Greedy Algorithm builds up a solution piece by piece, always choosing the next piece that offers the most immediate/local benefit without reconsidering past choices.
- Greedy Choice Property: A globally optimal solution can be reached by making locally optimal decisions.
- Optimal Substructure: An optimal solution to the problem contains optimal solutions to its subproblems.
- Exchange Argument: Show that any optimal solution can be gradually transformed into the greedy solution without worsening its quality.
- Greedy Stays Ahead: Show that at every intermediate step, the greedy solution's progress is at least as good as any alternative.
// Jump Game: Can you reach the last index?
bool canJump(vector<int>& nums) {
int maxReach = 0;
int n = nums.size();
for (int i = 0; i < n; ++i) {
if (i > maxReach) return false; // Cannot reach this index
maxReach = max(maxReach, i + nums[i]);
if (maxReach >= n - 1) return true;
}
return true;
}int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
int totalTank = 0;
int currentTank = 0;
int startingStation = 0;
for (size_t i = 0; i < gas.size(); ++i) {
int diff = gas[i] - cost[i];
totalTank += diff;
currentTank += diff;
if (currentTank < 0) {
// Cannot start from any station up to i
startingStation = i + 1;
currentTank = 0;
}
}
return totalTank >= 0 ? startingStation : -1;
}// Non-overlapping intervals: Max non-overlapping intervals = Min removals
int eraseOverlapIntervals(vector<vector<int>>& intervals) {
if (intervals.empty()) return 0;
// Sort by end time
sort(intervals.begin(), intervals.end(), [](const auto& a, const auto& b) {
return a[1] < b[1];
});
int count = 0;
int prevEnd = intervals[0][1];
for (size_t i = 1; i < intervals.size(); ++i) {
if (intervals[i][0] < prevEnd) {
count++; // Overlap -> remove current
} else {
prevEnd = intervals[i][1];
}
}
return count;
}Used when finding the smallest valid sequence/number
- Match the longest valid prefix of
numfrom right to left. - Increment the pivot digit
$D > \text{num}[i]$ . - Greedily construct the lexicographically smallest valid suffix of remaining length
$L$ by selecting the smallest feasible digit$d \in [1, 9]$ at each step.
When positions must satisfy Lipschitz/slope constraints (e.g.
- Sort restrictions by index and add boundary conditions
$(1, 0)$ and$(n, n - 1)$ . -
Left-to-Right Pass: Propagate growth limits forward:
$h_i = \min(h_i, h_{i-1} + (x_i - x_{i-1}))$ . -
Right-to-Left Pass: Propagate growth limits backward:
$h_i = \min(h_i, h_{i+1} + (x_{i+1} - x_i))$ . -
Triangular Peak: Between adjacent tight bounds
$(x_1, h_1)$ and$(x_2, h_2)$ , the peak height is$\lfloor \frac{(x_2 - x_1) + h_1 + h_2}{2} \rfloor$ .
When tasks require a minimum initial threshold
- Use an exchange argument on adjacent tasks
$A$ and$B$ : doing$A$ before$B$ is better if and only if$(minimum_A - actual_A) \ge (minimum_B - actual_B)$ . - Sort tasks in descending order of
$(minimum_i - actual_i)$ .
When constructing a lexicographically minimal string satisfying equality and inequality substring constraints:
- Mandatory Placements: Fix all mandatory equality constraints ('T'). Contradictions immediately imply infeasibility.
- Minimal Default Fill: Populate all unconstrained positions with the minimal character (
'a'). - Rightmost Breaking: For any violated inequality constraint ('F') where a substring matches the forbidden pattern, break the match by modifying the rightmost unconstrained position in that window to the next available character (
'b'), minimizing lexicographical penalty. - Validation: Run a final verification pass to ensure no later modifications violated earlier constraints.
When formatting text into justified fixed-width lines:
-
Greedy Fitting: Pack as many words as fit within
maxWidthsuch that word lengths plus mandatory single-space separators$\le \text{maxWidth}$ . - Left-Justification Exception: If the line contains a single word or is the last line of text, space words with 1 space and right-pad remaining spaces.
When an element's value must strictly exceed both its left and right neighbors subject to rating inequalities:
-
Left-to-Right Pass: Initialize all elements to baseline minimum (e.g. 1) and increment
$A[i] = A[i - 1] + 1$ whenever$R[i] > R[i - 1]$ . -
Right-to-Left Pass: Update
$A[i] = \max(A[i], A[i + 1] + 1)$ whenever$R[i] > R[i + 1]$ . - The
$\max$ operator maintains the previously satisfied left-neighbor condition while strictly enforcing the right-neighbor condition in$\mathcal{O}(N)$ time.
When extending the contiguous range of formable subset sums
-
Invariant: Maintain that all integers in
$[1, \text{miss} - 1]$ can be formed. -
Expansion with Existing Elements: If the next sorted element satisfies
$\text{nums}[i] \le \text{miss}$ , include it to expand reach to$[1, \text{miss} + \text{nums}[i] - 1]$ without gaps ($\text{miss} \gets \text{miss} + \text{nums}[i], i \gets i + 1$ ). -
Greedy Patching on Discontinuity: If
$\text{nums}[i] > \text{miss}$ (or input exhausted), patch$\text{miss}$ itself to double coverage to$[1, 2 \cdot \text{miss} - 1]$ ($\text{miss} \gets 2 \cdot \text{miss}, \text{patches} \gets \text{patches} + 1$ ). -
Complexity: Since reach doubles on each patch, at most
$\mathcal{O}(\log n)$ patches occur$\implies \mathcal{O}(M + \log n)$ time,$\mathcal{O}(1)$ space.
Pattern K: Multi-Constraint Edit Distance with Priority Deletion Allocation (Strong Password Checker)
When editing a sequence subject to simultaneous length bounds
-
Regime 1 (
$N < L_{\min}$ ): Inserts dominate; each insert simultaneously increases length, supplies missing character categories, and breaks runs$\implies \max(L_{\min} - N, \text{missingTypes})$ . -
Regime 2 (
$L_{\min} \le N \le L_{\max}$ ): Replacements dominate; each replacement simultaneously breaks a run and supplies missing categories$\implies \max(\sum \lfloor L_i / 3 \rfloor, \text{missingTypes})$ . -
Regime 3 (
$N > L_{\max}$ ):$D = N - L_{\max}$ deletions are mandatory. Greedily prioritize deletions that save the most replacements per unit cost:-
$L_i \pmod 3 == 0$ : 1 deletion saves 1 replacement. -
$L_i \pmod 3 == 1$ : 2 deletions save 1 replacement. -
$L_i \ge 3$ : 3 deletions save 1 replacement. - Final cost:
$D + \max(\text{remainingReplacements}, \text{missingTypes})$ .
-
When selecting at most
- Capital-Sorted Event Pointer: Sort projects ascendingly by required capital.
-
Dynamic Affordability Heap: Maintain a max-heap of profits for all projects with
$\text{capital}[i] \le w$ . - Greedy Selection: At each round, advance the capital pointer to unlock newly affordable projects into the heap, then pop the maximum profit.
-
Exchange Correctness: Taking the maximum profit strictly maximizes future capital
$w$ , expanding the pool of available projects monotonically. -
Complexity:
$\mathcal{O}(N \log N + k \log N)$ time and$\mathcal{O}(N)$ space.
When equalizing distributions along a 1D line with simultaneous unit transfers:
-
Feasibility Check: Ensure total sum
$S \pmod N == 0$ ; set target per unit to$S / N$ . -
Cut Bottleneck (Cross-Boundary Flow): Cumulative prefix balance
$\text{balance}[i] = \sum_{k=0}^i (\text{arr}[k] - \text{target})$ requires$|\text{balance}[i]|$ moves across boundary$(i, i+1)$ . -
Single Node Outflow Rate Bottleneck: A node with surplus
$\text{arr}[i] > \text{target}$ can only emit 1 unit per turn, requiring$\text{arr}[i] - \text{target}$ moves. -
Global Minimum:
$\max_{i} (\max(|\text{balance}[i]|, \text{arr}[i] - \text{target}))$ . -
Complexity:
$\mathcal{O}(N)$ time and$\mathcal{O}(1)$ space.
Pattern N: Earliest Deadline First with Regret-Based Max-Heap Duration Replacement (Course Schedule III)
When maximizing the number of scheduled jobs with durations
-
Earliest Deadline First (EDF) Ordering: Sort all jobs ascendingly by deadline
$D_i$ . -
Greedy Inclusion: If
$\text{currentTime} + d_i \le D_i$ , schedule job$i$ , increment$\text{currentTime} \gets \text{currentTime} + d_i$ , and push$d_i$ to a max-heap. -
Regret Replacement: If job
$i$ violates its deadline but$d_i < \text{maxHeap.top()}$ , replace the longest previously taken job:$$\text{currentTime} \gets \text{currentTime} + d_i - \text{maxHeap.top()}$$ Pop the longest duration and push$d_i$ . This preserves the cardinality of scheduled jobs while strictly reducingcurrentTime, creating maximal slack for subsequent jobs. -
Complexity:
$\mathcal{O}(N \log N)$ time and$\mathcal{O}(N)$ space.
- Greedy Fallacy: Ensure greedy choice property actually holds (e.g. standard 0/1 knapsack cannot be solved greedily, it requires DP).
- Sorting Criteria: Choosing whether to sort by start time, end time, or value ratio is crucial. Always verify with an exchange argument.
-
Empty Input / Single Element: Always verify behavior for
$N = 0$ or$N = 1$ . - Unrelaxed Restrictions: Calculating peaks on loose/unrelaxed restrictions overestimates peak height. Always perform two-pass relaxation first.
-
64-bit Overflow on Reach Range: Continuous interval expansion (
$\text{miss} \times 2$ ) can exceed$2^{31}-1$ ; always storemissaslong long.
| # | Title | Difficulty | Time | Space | Solution Link |
|---|---|---|---|---|---|
| 68 | Text Justification | Hard |
C++ | ||
| 135 | Candy | Hard |
C++ | ||
| 330 | Patching Array | Hard |
C++ | ||
| 420 | Strong Password Checker | Hard |
C++ | ||
| 502 | IPO | Hard |
C++ | ||
| 517 | Super Washing Machines | Hard |
C++ | ||
| 630 | Course Schedule III | Hard |
C++ | ||
| 871 | Minimum Number of Refueling Stops | Hard |
C++ | ||
| 936 | Stamping The Sequence | Hard |
C++ | ||
| 968 | Binary Tree Cameras | Hard |
C++ | ||
| 1665 | Minimum Initial Energy to Finish Tasks | Hard |
C++ | ||
| 1840 | Maximum Building Height | Hard |
C++ | ||
| 3348 | Smallest Divisible Digit Product II | Hard |
C++ | ||
| 3474 | Lexicographically Smallest Generated String | Hard |
C++ |