Greedy Algorithms

Greedy অ্যালগরিদম

Read: ~40 min Intermediate 7 practice problems Live C++ runner

1. The Greedy Idea — Local Choice, Global Hope

A greedy algorithm builds a solution one piece at a time, always making the choice that looks best right now, never reconsidering. When the problem has the right structure, that local rule produces a globally optimal answer — and it is fast: usually O(n log n) or even O(n).

Greedy algorithm এক ধাপে এক টুকরো করে সমাধান তৈরি করে — প্রতিবার এমন সিদ্ধান্ত নেয় যা এই মুহূর্তে সবচেয়ে ভালো, পরে আর সেটা পাল্টায় না। সঠিক structure-এর সমস্যায় এই local rule globally optimal solution দেয়, এবং সাধারণত খুব দ্রুত — O(n log n) বা O(n)।
Module insight Greedy অনেক সময় ভুল ফলাফল দেয়। প্রমাণ ছাড়া greedy ব্যবহার করা মানে ভাগ্যের উপর নির্ভর করা — তাই প্রতিটি greedy-র পেছনে exchange argument বা matroid theory-র মতো একটি প্রমাণ থাকা দরকার।

2. The Two Properties That Make Greedy Work

A problem is solvable by greedy if and only if it has both:

  • Greedy-choice property — a global optimum can always be obtained by making a locally optimal choice.
  • Optimal substructure — the optimal solution to the whole problem contains optimal solutions to its subproblems.
একটি সমস্যায় greedy কাজ করবে যদি দুটি বৈশিষ্ট্য থাকে — (১) greedy-choice property: প্রতিটি স্থানীয়ভাবে সেরা সিদ্ধান্ত globally optimal solution-এ পরিণত হয়; (২) optimal substructure: পুরো সমস্যার optimal solution উপ-সমস্যাগুলোর optimal solution থেকেই গঠিত।
Exchange argument — to prove a greedy is correct, take any optimal solution OPT and show you can swap one of its choices for the greedy's choice without making OPT worse. Repeat until OPT becomes the greedy solution. Therefore greedy ≤ OPT, and since OPT is optimal, greedy = OPT.

3. Activity Selection — The Classic

Given n activities each with a start and finish time, select the maximum number that can be done by a single person (no two overlap). The right greedy: sort by finish time, then iterate, picking each activity whose start ≥ last picked finish.

n-টি কার্যক্রম আছে — প্রতিটির start ও finish সময় দেওয়া। একজন মানুষ overlap ছাড়া সর্বাধিক কতগুলো করতে পারবে? সঠিক greedy: finish time অনুযায়ী sort করুন, তারপর প্রতিটিতে এমন activity বেছে নিন যার start ≥ আগের finish।
0 2 4 6 8 10 12 14 16 18 A1 [0,4] ✓ A2 [1,5] A3 [4,8] ✓ A4 [6,9] A5 [8,12] ✓ A6 [10,14] A7 [12,16] ✓ Figure 32.1 — Activities sorted by finish time. Greedy picks A1, A3, A5, A7 (4 activities). The grey ones are skipped because they overlap a more "compact" choice.
activity.cpp
#include <bits/stdc++.h>
using namespace std;

int main() {
    vector<pair<int,int>> act = {
        {0,4}, {1,5}, {4,8}, {6,9},
        {8,12}, {10,14}, {12,16}
    };
    // Sort by finish time (.second)
    sort(act.begin(), act.end(), [](auto& a, auto& b){
        return a.second < b.second;
    });
    int count = 0, lastEnd = INT_MIN;
    for (auto& [s, e] : act) {
        if (s >= lastEnd) {
            cout << "Pick [" << s << "," << e << "]\n";
            lastEnd = e;
            ++count;
        }
    }
    cout << "Total selected = " << count << "\n";
}
Why earliest-finish-first works. Suppose OPT does not pick activity 1 (the one finishing first). Replace OPT's first activity x with 1 — since 1 finishes no later than x, the rest of OPT still fits. So |OPT'| = |OPT| and OPT' includes activity 1. Recurse. ∎

4. Huffman Coding — Optimal Prefix Codes

Given letter frequencies, build a prefix-free binary code that minimises the total encoded length. Huffman's algorithm repeatedly combines the two least-frequent nodes into a new internal node — using a min-heap, that's O(n log n).

অক্ষর-frequency দেওয়া থাকলে এমন prefix-free binary code চাই যা মোট encoded length সবচেয়ে ছোট রাখে। Huffman বারবার সবচেয়ে কম frequency-র দুটি node নিয়ে একটি নতুন internal node বানায় — min-heap দিয়ে O(n log n)।
100 a:45 55 0 1 25 30 0 1 c:12 b:13 d:14 e:16 Figure 32.2 — Huffman tree for frequencies a:45, b:13, c:12, d:14, e:16. Codes: a=0, c=100, b=101, d=110, e=111.
huffman.cpp
#include <bits/stdc++.h>
using namespace std;

struct Node { int freq; char ch; Node *l=nullptr, *r=nullptr; };
struct Cmp { bool operator()(Node* a, Node* b){ return a->freq > b->freq; } };

void codes(Node* n, string p, map<char,string>& out) {
    if (!n) return;
    if (!n->l && !n->r) { out[n->ch] = p.empty() ? "0" : p; return; }
    codes(n->l, p+"0", out);
    codes(n->r, p+"1", out);
}

int main() {
    vector<pair<char,int>> freq = {{'a',45},{'b',13},{'c',12},{'d',14},{'e',16}};
    priority_queue<Node*, vector<Node*>, Cmp> pq;
    for (auto& [c, f] : freq) pq.push(new Node{f, c});
    while (pq.size() > 1) {
        Node* a = pq.top(); pq.pop();
        Node* b = pq.top(); pq.pop();
        Node* parent = new Node{a->freq + b->freq, '#', a, b};
        pq.push(parent);
    }
    map<char,string> out;
    codes(pq.top(), "", out);
    for (auto& [c, code] : out)
        cout << c << " -> " << code << "\n";
}

5. Fractional vs 0/1 Knapsack — When Greedy Breaks

Fractional knapsack (you may take any fraction of an item): sort by value/weight ratio descending, take as much of each item as fits — provably optimal. 0/1 knapsack (you must take or leave): the same greedy may be arbitrarily bad. That's a DP problem (Lecture 35).

Fractional knapsack-এ যেকোনো অংশ নেওয়া যায় — value/weight ratio অনুযায়ী sort করে greedy চালালেই optimal। কিন্তু 0/1 knapsack-এ পুরো item নিতে হয় বা ছাড়তে হয় — সেই greedy ভয়াবহভাবে ব্যর্থ হতে পারে। সেটি DP-র সমস্যা (Lecture 35)।

✅ Fractional Knapsack — Greedy Works

Sort items by value/weight ratio. Take items in order; if next won't fit fully, take a fraction. O(n log n).

⚠️ 0/1 Knapsack — Greedy Fails

Items: (60, 10), (100, 20), (120, 30), W=50. Greedy by ratio picks first two (160). DP picks last two (220).

fractional_knapsack.cpp
#include <bits/stdc++.h>
using namespace std;

int main() {
    vector<pair<int,int>> items = {{60,10}, {100,20}, {120,30}};
    int W = 50;
    sort(items.begin(), items.end(), [](auto& a, auto& b){
        return (double)a.first/a.second > (double)b.first/b.second;
    });
    double value = 0;
    for (auto& [v, w] : items) {
        if (W >= w) { value += v; W -= w; }
        else { value += v * ((double)W / w); break; }
    }
    cout << "Max value = " << value << "\n";
}

6. Famous Greedy Failures

ProblemGreedy IdeaVerdict
Coin change with arbitrary denominationsAlways pick largest coin ≤ remaining❌ Fails for {1,3,4} with target 6: greedy = 4+1+1, optimal = 3+3
0/1 knapsackSort by value/weight❌ Counter-example above
Travelling SalesmanAlways go to nearest unvisited city❌ NP-hard; greedy can be 25%+ worse than optimal
Longest path in DAGPick highest-weight edge each step❌ Use DP topologically instead
অনেক বিখ্যাত সমস্যায় greedy সঠিক উত্তর দেয় না — যেমন সাধারণ coin denomination-এ (Bangladesh-এর ১,২,৫,১০,২০,৫০,১০০ টাকার কয়েনে greedy চলে কারণ এটি canonical, কিন্তু {১,৩,৪}-এ চলে না), 0/1 knapsack, TSP, longest path।

7. Practice Problems

প্রতিটি সমাধানের আগে নিজে চেষ্টা করুন।
  1. Jump Game — given nums[i] = max jump length from i, can you reach the last index?
    প্রতিটি index থেকে সর্বাধিক jump length দেওয়া। শেষ index-এ পৌঁছানো যাবে কিনা?
    ✨ Show Answer
    jump.cpp
    #include <bits/stdc++.h>
    using namespace std;
    int main(){
        vector<int> a = {2,3,1,1,4};
        int reach = 0;
        for (int i = 0; i < (int)a.size(); ++i) {
            if (i > reach) { cout << "NO\n"; return 0; }
            reach = max(reach, i + a[i]);
        }
        cout << "YES\n";
    }
  2. Minimum number of platforms needed at Kamalapur Railway Station given arrival/departure times.
    Kamalapur Railway Station-এ ট্রেনের arrival/departure সময় দেওয়া। সর্বনিম্ন কতগুলো platform লাগবে?
    ✨ Show Answer
    platforms.cpp
    #include <bits/stdc++.h>
    using namespace std;
    int main(){
        vector<int> arr = {900, 940, 950, 1100, 1500, 1800};
        vector<int> dep = {910, 1200, 1120, 1130, 1900, 2000};
        sort(arr.begin(), arr.end());
        sort(dep.begin(), dep.end());
        int i = 0, j = 0, cur = 0, ans = 0;
        while (i < (int)arr.size()) {
            if (arr[i] <= dep[j]) { ++cur; ++i; ans = max(ans, cur); }
            else { --cur; ++j; }
        }
        cout << "Platforms = " << ans << "\n";
    }
  3. Gas station circuit — given gas[i] and cost[i] to travel to next station, find a starting station to complete the loop.
    প্রতিটি station-এ gas[i] পাওয়া যায় এবং পরের station-এ যেতে cost[i] লাগে। কোন station থেকে শুরু করলে পুরো বৃত্ত শেষ হবে?
    ✨ Show Answer
    gas.cpp
    #include <bits/stdc++.h>
    using namespace std;
    int main(){
        vector<int> gas = {1,2,3,4,5}, cost = {3,4,5,1,2};
        int tank=0, total=0, start=0;
        for (int i=0; i < (int)gas.size(); ++i) {
            int d = gas[i] - cost[i];
            tank += d; total += d;
            if (tank < 0) { start = i+1; tank = 0; }
        }
        cout << (total >= 0 ? start : -1) << "\n";
    }
  4. Assign Cookies — give each child the smallest cookie that satisfies their greed. Maximise satisfied children.
    প্রতিটি শিশুকে এমন কুকি দিন যা তার greed পূরণ করে। সর্বাধিক কতজন সন্তুষ্ট হবে?
    ✨ Show Answer
    cookies.cpp
    #include <bits/stdc++.h>
    using namespace std;
    int main(){
        vector<int> g = {1,2,3}, s = {1,1,2,3};
        sort(g.begin(), g.end());
        sort(s.begin(), s.end());
        int i=0, j=0;
        while (i<(int)g.size() && j<(int)s.size()) {
            if (s[j] >= g[i]) ++i;
            ++j;
        }
        cout << i << "\n";
    }
  5. Minimum coins for canonical Bangladesh denominations {1,2,5,10,20,50,100,500} to make amount 678.
    Bangladesh-এর {১,২,৫,১০,২০,৫০,১০০,৫০০} টাকার কয়েনে ৬৭৮ টাকা সর্বনিম্ন কতগুলো কয়েনে দেওয়া যাবে?
    ✨ Show Answer
    coins.cpp
    #include <bits/stdc++.h>
    using namespace std;
    int main(){
        vector<int> d = {500,100,50,20,10,5,2,1};
        int amt = 678, cnt = 0;
        for (int c : d) while (amt >= c) { amt -= c; ++cnt; }
        cout << "Coins = " << cnt << "\n";
    }

    Output: Coins = 7 (500 + 100 + 50 + 20 + 5 + 2 + 1 = 678).

  6. Job sequencing with deadlines — each job has profit p[i] and deadline d[i]. Maximise profit, each job takes 1 time unit.
    প্রতিটি job-এর deadline ও profit আছে। প্রতিটি ১ unit সময় নেয়। সর্বাধিক profit বের করুন।
    ✨ Show Answer
    job_seq.cpp
    #include <bits/stdc++.h>
    using namespace std;
    int main(){
        vector<tuple<int,int>> jobs = {{100,2},{19,1},{27,2},{25,1},{15,3}};
        sort(jobs.begin(), jobs.end(), greater<>());
        vector<bool> slot(10, false);
        int profit = 0;
        for (auto& [p, d] : jobs) {
            for (int t = d; t > 0; --t)
                if (!slot[t]) { slot[t] = true; profit += p; break; }
        }
        cout << "Max profit = " << profit << "\n";
    }
  7. Lemonade change — customers pay 5, 10, or 20 taka for a 5-taka drink. Can you give correct change to everyone?
    ৫ টাকার লেবু-পানি — customer ৫, ১০, বা ২০ টাকা দেয়। সবাইকে সঠিক change দিতে পারবেন?
    ✨ Show Answer
    lemonade.cpp
    #include <bits/stdc++.h>
    using namespace std;
    int main(){
        vector<int> bills = {5,5,10,10,20};
        int five=0, ten=0;
        for (int b : bills) {
            if (b == 5) ++five;
            else if (b == 10) { if (!five) { cout << "FAIL\n"; return 0; } --five; ++ten; }
            else { if (ten && five) { --ten; --five; } else if (five >= 3) five -= 3; else { cout << "FAIL\n"; return 0; } }
        }
        cout << "OK\n";
    }

Summary — Module 32

Greedy algorithms make the locally best choice at every step and never look back. They are short, fast, and beautiful — when they work. Activity selection (sort by finish time), Huffman coding (combine smallest), and fractional knapsack (sort by ratio) are the canonical wins. But for 0/1 knapsack and arbitrary coin systems greedy fails, and the only safe practice is to prove correctness by an exchange argument before trusting any greedy.

Greedy প্রতিটি ধাপে স্থানীয়ভাবে সেরা সিদ্ধান্ত নেয় — সঠিক structure-এ এটি দ্রুত ও সংক্ষিপ্ত optimal solution দেয়। Activity selection, Huffman, fractional knapsack — তিনটিই canonical উদাহরণ। কিন্তু 0/1 knapsack ও সাধারণ coin denomination-এ greedy ব্যর্থ — তাই প্রতিটি greedy-র correctness exchange argument দিয়ে আগে প্রমাণ করুন।

Next Module → Divide and Conquer Patterns — closest pair, Karatsuba, Strassen।