⚙️AlgorithmIntermediate

Greedy - Exchange Argument

Key Points

  • The exchange argument proves a greedy algorithm is optimal by swapping out-of-order choices in any supposed optimal solution until it matches the greedy one without making it worse.
  • You assume there exists an optimal solution O that differs from the greedy solution G, find the first difference, and show a local swap improves or preserves O.
  • This technique shines in scheduling problems where the objective is linear in completion times or lateness.
  • Classic results include SPT (shortest processing time first) minimizing sum of completion times and Smith’s rule minimizing weighted sum of completion times.
  • For interval scheduling, choosing intervals by earliest finishing time can be justified by an exchange-style structural lemma.
  • Always check that your swap keeps the solution feasible (no overlaps, respects precedence, etc.).
  • Correct tie-breaking and safe comparators (e.g., cross-multiplying with 128-bit integers) avoid subtle counterexamples and overflow.
  • Most exchange-argument-based greedy solutions run in O(n n) due to sorting and O(n) for a verification pass.

Prerequisites

  • Asymptotic notation (Big-O)To analyze the runtime and space of sort-and-scan greedy algorithms.
  • Sorting and comparatorsGreedy orders are typically implemented by sorting with a custom comparator.
  • Summations and linearityObjectives like \sum C_i or \sum w_i C_i are sums; understanding how swaps change these sums is essential.
  • Proof by contradiction and invariantsThe exchange argument relies on assuming a differing optimal solution and maintaining feasibility during swaps.
  • Scheduling basicsConcepts like processing time, completion time, due date, and interval feasibility are core to examples.
  • Integer overflow and numeric stabilitySafe cross-multiplication in weighted comparators prevents overflow and precision bugs.
  • Greedy algorithmsUnderstanding how a local rule leads to a full solution helps identify when exchange arguments apply.

Detailed Explanation

Tap terms for definitions

01Overview

The exchange argument is a proof technique used to show that a greedy algorithm actually returns an optimal solution. Rather than trying to analyze all solutions directly, you start with any optimal solution O and the greedy solution G. You locate the first position where they differ and show that you can "exchange" (swap) the item chosen by O with the greedy choice without making the objective value worse. Repeating this operation eventually transforms O into G while preserving optimality at each step, which implies G is optimal. This approach is clean and powerful because it reduces correctness to a local two-item comparison argument, often called a pairwise-exchange lemma. The technique is common in scheduling: for instance, sorting jobs by processing time p_i minimizes the sum of completion times, sorting by p_i/w_i (Smith’s rule) minimizes the weighted sum of completion times, and sorting by due dates d_i minimizes maximum lateness. In interval scheduling, picking the earliest finishing interval can be justified with a closely related exchange-style structural argument. The key ingredients are a linear or well-behaved objective that decomposes nicely across positions, and feasibility that is preserved under adjacent swaps. When these hold, you can prove that every inversion (a pair in the wrong order) is correctable by a swap that strictly improves or preserves the objective.

02Intuition & Analogies

Imagine lining up students by height for a photo to reduce how much faces are blocked. If a taller student stands in front of a shorter one, swapping them improves the view. You don’t need to optimize the entire line at once—just fix any adjacent pair that is out of order. Repeat this until no more fixes are needed, and you get an optimal arrangement. That’s the spirit of the exchange argument: solve a global problem by proving that local swaps never hurt and often help. Another analogy is packing your backpack in the order you’ll need items during the day so you minimize time searching. If you discover you often need your notebook before your water bottle, swapping their positions reduces repeated search time. Over time, consistent local improvements implicitly produce a globally efficient packing order. In scheduling, each job contributes to the total cost through its completion time. If a longer job is placed before a shorter one, the shorter job’s completion time is unnecessarily increased. Swapping these two reduces the sum of completion times by exactly the difference in their lengths. Weighted versions are similar: a job with high weight (importance) should not be delayed behind a relatively low-weight, long job; a simple ratio comparison tells you which should go first. The magic is that you don’t need to prove a complex global structure—just that any incorrectly ordered neighboring pair can be fixed by a swap without breaking feasibility. With that, repeated local fixes yield the greedy order, hence optimal.

03Formal Definition

Let G be the sequence produced by a greedy algorithm that makes a myopic choice at each step according to a rule R (e.g., sort by a key and schedule in that order). Let f(·) be the objective function to minimize (or maximize) over all feasible sequences. An exchange argument typically proceeds by contradiction: assume there exists an optimal solution O that differs from G. Let k be the first position where G and O differ; denote by g the item G places at position k, and let O place some item there. The pairwise-exchange lemma states that there exists an exchange (usually swapping adjacent items) that moves g into position k within O without increasing f(O) and while preserving feasibility. After this exchange, the prefix of O up to k matches G. Repeating this process for k+1, k+2, … transforms O into G via a sequence of exchanges, all of which do not increase f. Since O was optimal, f(G) ≤ f(O) = optimum, implying G is optimal. In many scheduling problems with objective functions linear in completion times, this lemma reduces to checking two neighboring jobs i and j and proving that if R prescribes i before j, then placing i before j weakly dominates the reverse order. Formally, one shows f(…i j…) ≤ f(…j i…) under the rule’s comparison (e.g., , or /). This local dominance over all adjacent inversions ensures global optimality.

04When to Use

Use the exchange argument when: (1) your solution is a permutation/ordering of items, (2) the objective decomposes cleanly across positions (often linear in completion times, lateness, or conflicts), and (3) feasibility is preserved by swapping adjacent items. Classic applications include: minimizing sum of completion times via SPT (sort by processing time), minimizing weighted sum of completion times via Smith’s rule (sort by p_i/w_i), minimizing maximum lateness via EDD (sort by due date), and selecting a maximum number of non-overlapping intervals by earliest finish time. The method is also applicable to certain matroid or matroid-like settings where a greedy exchange property holds. Avoid or be cautious when the objective is highly non-local or swaps break feasibility, e.g., 0/1 knapsack (item inclusion, not ordering) where the classic fractional-greedy proof does not carry over to 0/1, or when precedence constraints forbid swaps. If you can articulate a simple two-item inequality that justifies the greedy comparator and you can argue any schedule can be transformed into the greedy order without harming the objective, you likely have an exchange-argument problem. Competitive programming problems in the 1400–1800 range often hide such a pairwise-dominance inequality.

⚠️Common Mistakes

Common pitfalls include: (1) Proving only that a single swap helps in one special case but not arguing that any optimal solution can be transformed completely into the greedy one via repeated swaps. Always include the “first difference” step and the iterative transformation. (2) Ignoring feasibility: swaps can create overlaps (interval scheduling with fixed start times), violate deadlines, or break resource constraints. Explicitly state and verify that adjacent swaps keep the solution feasible. (3) Using an incorrect comparator: for weighted objectives, sorting by weight alone or by processing time alone can be wrong; derive the comparator from a two-item inequality (e.g., p_i/w_i ≤ p_j/w_j for Smith’s rule). (4) Floating-point precision errors in comparators for ratios; prefer cross-multiplication with wide integers (e.g., __int128) to avoid overflow and ties. (5) Mishandling ties: if equality holds, either order is fine, but deterministic tie-breaking (by id) can stabilize outputs and proofs. (6) Off-by-one errors in computing completion times or lateness (C_k accumulates all prior processing times; lateness is L_k = C_k − d_k). (7) Confusing average with sum: minimizing average completion time is equivalent to minimizing the sum, but ensure you reason about the correct objective. Address these by writing the two-item exchange formula explicitly, checking constraints after swaps, and implementing safe, stable comparators.

Key Formulas

Sum of completion times under an order

Explanation: If jobs are ordered by some permutation (i), the total of completion times equals each processing time multiplied by the number of jobs at or after it. This shows why putting shorter jobs earlier reduces the sum.

Two-job swap effect (unweighted)

Explanation: Given a common start time, swapping a shorter job i () before a longer job j () reduces the sum of their completion times by - . If , placing i first is never worse.

Two-job swap effect (weighted)

Explanation: With weights and , i before j is no worse than j before i if and only if , equivalently / / (Smith's rule).

Lateness and maximum lateness

Explanation: Lateness measures how far after the due date a job finishes (negative means early). Minimizing the maximum lateness is achieved by ordering by earliest due date (EDD) via an exchange argument.

Sorting complexity

Explanation: Most exchange-argument greedy algorithms reduce to sorting by a key and then scanning. Sorting dominates the time complexity.

Interval scheduling rule

Explanation: Choosing intervals by earliest finishing time yields a maximum-size compatible subset. An exchange-style structural proof shows an optimal solution can be adjusted to match the greedy picks.

Weighted sum of completion times objective

Explanation: This is the total weighted completion time to minimize. Sorting by / (Smith’s rule) minimizes this sum on a single machine without preemption.

Typical resource bounds

Explanation: When implemented with sorting and linear passes, time is O(n log n) and space is O(n) for storing items and outputs. In-place sorts can reduce auxiliary space.

Complexity Analysis

For most exchange-argument greedy algorithms on n items, the dominant step is sorting by the comparator derived from the two-item inequality. Comparison-based sorting takes O(n n) time in the worst case. After sorting, a single pass computes the objective or constructs the solution in O(n) time: for scheduling, we accumulate completion times; for interval scheduling, we scan to select compatible intervals. Thus, total time is O(n n) with small constant factors. Space usage depends on the data representation. If we store items in a vector and sort in place, auxiliary space is O(1) to O( n) (implementation-dependent), with O(n) for the input itself. If we must retain original indices or build an explicit solution set, total space is O(n). In weighted-comparator implementations using cross-multiplication, to avoid 64-bit overflow when comparing a.p * b.w vs. b.p * a.w, we can use 128-bit intermediates; this does not change asymptotic bounds but slightly increases constant factors. For interval scheduling, no extra data structures beyond the sorted array are needed, maintaining O(n) additional space. Overall, exchange-argument greedy methods tend to be very efficient practically because they are sort-and-scan. Edge cases that can worsen constants include heavy tie-breaking logic and stability requirements, but they do not change the O(n n) time and O(n) space asymptotics.

Code Examples

SPT: Minimize sum of completion times by sorting by processing time
1#include <bits/stdc++.h>
2using namespace std;
3
4struct Job {
5 int id; // original index
6 long long p; // processing time
7};
8
9int main() {
10 ios::sync_with_stdio(false);
11 cin.tie(nullptr);
12
13 // Example jobs (processing times)
14 vector<Job> jobs = {{0, 3}, {1, 1}, {2, 2}, {3, 7}};
15
16 // Greedy: Shortest Processing Time first (SPT)
17 sort(jobs.begin(), jobs.end(), [](const Job& a, const Job& b){
18 if (a.p != b.p) return a.p < b.p; // increasing p
19 return a.id < b.id; // stable tie-break by id
20 });
21
22 long long time = 0; // current time (prefix sum of p)
23 long long sumC = 0; // sum of completion times
24 vector<int> order; order.reserve(jobs.size());
25
26 for (const auto& j : jobs) {
27 time += j.p; // job completes at current time + p
28 sumC += time; // add completion time
29 order.push_back(j.id);
30 }
31
32 cout << "Order (by original id): ";
33 for (int i = 0; i < (int)order.size(); ++i) {
34 if (i) cout << ' ';
35 cout << order[i];
36 }
37 cout << "\n";
38 cout << "Sum of completion times = " << sumC << "\n";
39
40 // Note: Exchange argument justification (pairwise):
41 // If two adjacent jobs have p_i > p_j, swapping them decreases total sum by (p_i - p_j).
42 // Repeating swaps (like bubble sort inversions) yields SPT which is optimal.
43
44 return 0;
45}
46

We sort jobs by processing time p_i ascending (SPT). The total of completion times is minimized because any inversion (longer before shorter) can be swapped to reduce the sum by exactly p_i - p_j. Accumulating a running time computes each completion time.

Time: O(n log n)Space: O(n)
Smith’s Rule: Minimize weighted sum of completion times by sorting by p_i / w_i
1#include <bits/stdc++.h>
2using namespace std;
3
4struct Job {
5 int id;
6 long long p; // processing time
7 long long w; // weight (importance)
8};
9
10// Safe comparator for p_i / w_i without floating point: compare a.p * b.w vs b.p * a.w
11// Use 128-bit to avoid overflow when multiplying two 64-bit numbers.
12bool smithCmp(const Job& a, const Job& b) {
13 __int128 lhs = (__int128)a.p * (__int128)b.w;
14 __int128 rhs = (__int128)b.p * (__int128)a.w;
15 if (lhs != rhs) return lhs < rhs; // increasing p/w
16 return a.id < b.id; // tie-breaker for stability
17}
18
19int main() {
20 ios::sync_with_stdio(false);
21 cin.tie(nullptr);
22
23 // Example jobs (p, w)
24 vector<Job> jobs = {
25 {0, 6, 3}, // ratio 2.0
26 {1, 3, 1}, // ratio 3.0
27 {2, 5, 5}, // ratio 1.0
28 {3, 4, 2} // ratio 2.0
29 };
30
31 sort(jobs.begin(), jobs.end(), smithCmp);
32
33 long long time = 0;
34 long long weightedSum = 0;
35 vector<int> order; order.reserve(jobs.size());
36
37 for (const auto& j : jobs) {
38 time += j.p;
39 weightedSum += j.w * time; // accumulate w_i * C_i
40 order.push_back(j.id);
41 }
42
43 cout << "Order (by original id): ";
44 for (int i = 0; i < (int)order.size(); ++i) {
45 if (i) cout << ' ';
46 cout << order[i];
47 }
48 cout << "\nWeighted sum of completion times = " << weightedSum << "\n";
49
50 // Exchange argument: For two jobs i, j, prefer i before j iff w_j * p_i <= w_i * p_j,
51 // equivalently p_i / w_i <= p_j / w_j. Swapping any violating adjacent pair reduces \sum w_k C_k.
52
53 return 0;
54}
55

To minimize the weighted sum of completion times, sort by increasing p_i/w_i (Smith’s rule). The pairwise-exchange inequality w_j p_i ≤ w_i p_j guarantees that any inversion can be fixed by a swap, leading to the greedy order. Using 128-bit intermediates avoids overflow in cross-multiplication.

Time: O(n log n)Space: O(n)
Interval Scheduling: Maximum number of non-overlapping intervals by earliest finish time
1#include <bits/stdc++.h>
2using namespace std;
3
4struct Interval {
5 int id;
6 int s, f; // start and finish times
7};
8
9int main() {
10 ios::sync_with_stdio(false);
11 cin.tie(nullptr);
12
13 vector<Interval> a = {
14 {0, 1, 3}, {1, 2, 5}, {2, 4, 7}, {3, 1, 2}, {4, 6, 9}, {5, 8, 10}
15 };
16
17 sort(a.begin(), a.end(), [](const Interval& x, const Interval& y){
18 if (x.f != y.f) return x.f < y.f; // earliest finishing time first
19 return x.s < y.s; // tie-break by start time
20 });
21
22 vector<int> chosen;
23 int lastFinish = INT_MIN; // or -infinity
24 for (const auto& it : a) {
25 if (it.s >= lastFinish) {
26 chosen.push_back(it.id);
27 lastFinish = it.f;
28 }
29 }
30
31 cout << "Chosen interval ids: ";
32 for (int i = 0; i < (int)chosen.size(); ++i) {
33 if (i) cout << ' ';
34 cout << chosen[i];
35 }
36 cout << "\n";
37
38 // Exchange-style structural proof sketch:
39 // Among all optimal solutions, take one with the earliest finishing first interval.
40 // If its first interval finishes later than the greedy's first choice, exchange them: feasibility holds and size is preserved.
41 // Repeating aligns the optimal solution with the greedy one.
42
43 return 0;
44}
45

Sorting by earliest finish time and greedily picking compatible intervals yields a maximum-size set. A structural exchange argument shows any optimal solution can be modified to match the greedy’s first choice and then proceed inductively.

Time: O(n log n)Space: O(n)
EDD: Minimize maximum lateness by sorting by due date
1#include <bits/stdc++.h>
2using namespace std;
3
4struct Job {
5 int id;
6 long long p; // processing time
7 long long d; // due date
8};
9
10int main() {
11 ios::sync_with_stdio(false);
12 cin.tie(nullptr);
13
14 vector<Job> jobs = {
15 {0, 3, 4}, {1, 2, 5}, {2, 7, 20}, {3, 4, 8}
16 };
17
18 sort(jobs.begin(), jobs.end(), [](const Job& a, const Job& b){
19 if (a.d != b.d) return a.d < b.d; // earliest due date first
20 return a.id < b.id;
21 });
22
23 long long t = 0;
24 long long Lmax = LLONG_MIN;
25 vector<int> order; order.reserve(jobs.size());
26
27 for (const auto& j : jobs) {
28 t += j.p; // completion time C_j
29 long long L = t - j.d; // lateness
30 Lmax = max(Lmax, L);
31 order.push_back(j.id);
32 }
33
34 cout << "Order (by original id): ";
35 for (int i = 0; i < (int)order.size(); ++i) {
36 if (i) cout << ' ';
37 cout << order[i];
38 }
39 cout << "\nMax lateness Lmax = " << Lmax << "\n";
40
41 // Exchange argument: If two adjacent jobs are out of EDD order (d_i > d_j), swapping them cannot increase L_max.
42 // Therefore, repeatedly fixing inversions yields EDD, which minimizes maximum lateness.
43
44 return 0;
45}
46

We sort jobs by earliest due date (EDD) and compute lateness along the schedule. A pairwise exchange shows that any adjacent inversion by due date does not increase maximum lateness, so EDD is optimal for minimizing L_max.

Time: O(n log n)Space: O(n)