AlgorithmsAlgorithms

How to solve problems step by step, and how to measure how fast a solution is.ধাপে ধাপে problem solve করার উপায়, আর কোন solution কত fast তা মাপার নিয়ম।

1. Complexity and Big-O1. Complexity আর Big-O

An algorithm is a step-by-step recipe to solve a problem. Two different recipes can solve the same problem. But one can be much faster. Complexity analysis tells us how fast (time complexity) and how much memory (space complexity) an algorithm needs.

We do not measure time in seconds. Seconds depend on the computer. Instead, we count basic steps (comparisons, additions, assignments) as a function of the input size \( n \). Then we look at how this count grows when \( n \) becomes big.

Algorithm হলো কোনো problem solve করার step-by-step recipe। একই problem-এর জন্য দুইটা আলাদা recipe থাকতে পারে। কিন্তু একটা অনেক বেশি fast হতে পারে। Complexity analysis আমাদের বলে একটা algorithm কত time (time complexity) আর কত memory (space complexity) নেয়।

আমরা second দিয়ে time মাপি না। কারণ second computer-এর উপর নির্ভর করে। তার বদলে আমরা input size \( n \)-এর function হিসেবে basic step গুনি (comparison, addition, assignment)। তারপর দেখি \( n \) বড় হলে এই count কীভাবে বাড়ে।

Big-O, Big-Omega, Big-Theta (simple meaning)Big-O, Big-Omega, Big-Theta (সহজ অর্থ)

  • Big-O \( O(g(n)) \) — upper bound. The algorithm takes at most about \( g(n) \) steps (for large \( n \)). "It will not be slower than this."
  • Big-Omega \( \Omega(g(n)) \) — lower bound. It takes at least about \( g(n) \) steps. "It will not be faster than this."
  • Big-Theta \( \Theta(g(n)) \) — tight bound. Both upper and lower bound match. "It takes exactly about \( g(n) \) steps."

Formal (but keep it simple): \( f(n) = O(g(n)) \) means there are constants \( c > 0 \) and \( n_0 \) so that \( f(n) \le c \cdot g(n) \) for all \( n \ge n_0 \). We ignore constant factors and small terms. So \( 3n^2 + 10n + 5 = O(n^2) \).

  • Big-O \( O(g(n)) \) — upper bound। Algorithm-টা বড় \( n \)-এর জন্য সর্বোচ্চ প্রায় \( g(n) \) step নেয়। "এর চেয়ে slow হবে না।"
  • Big-Omega \( \Omega(g(n)) \) — lower bound। এটা কমপক্ষে প্রায় \( g(n) \) step নেয়। "এর চেয়ে fast হবে না।"
  • Big-Theta \( \Theta(g(n)) \) — tight bound। Upper আর lower bound দুটোই মিলে যায়। "এটা ঠিক প্রায় \( g(n) \) step নেয়।"

Formal ভাবে (সহজ করে): \( f(n) = O(g(n)) \) মানে এমন constant \( c > 0 \) আর \( n_0 \) আছে যেন সব \( n \ge n_0 \)-এর জন্য \( f(n) \le c \cdot g(n) \) হয়। আমরা constant factor আর ছোট term বাদ দিই। তাই \( 3n^2 + 10n + 5 = O(n^2) \)।

Common growth rates (slow-growing → fast-growing)Common growth rate (ধীরে বাড়ে → দ্রুত বাড়ে)

\[ O(1) \lt O(\log n) \lt O(n) \lt O(n \log n) \lt O(n^2) \lt O(n^3) \lt O(2^n) \lt O(n!) \]
ComplexityComplexityNameনামExampleExampleSteps if n = 1000n = 1000 হলে step
\( O(1) \)ConstantConstantArray index accessArray index access1
\( O(\log n) \)LogarithmicLogarithmicBinary searchBinary search≈ 10
\( O(n) \)LinearLinearLinear searchLinear search1000
\( O(n \log n) \)LinearithmicLinearithmicMerge sortMerge sort≈ 10,000
\( O(n^2) \)QuadraticQuadraticBubble sortBubble sort106
\( O(2^n) \)ExponentialExponentialAll subsetsসব subset বের করা21000 (huge)
\( O(n!) \)FactorialFactorialAll permutationsসব permutationimpossibleঅসম্ভব

How to find complexity of loopsLoop-এর complexity কীভাবে বের করবেন

// Single loop: runs n times            → O(n)
for (i = 0; i < n; i++) sum += a[i];

// Nested loop: n * n steps             → O(n^2)
for (i = 0; i < n; i++)
    for (j = 0; j < n; j++) count++;

// Dependent nested loop: 1+2+...+n = n(n+1)/2   → O(n^2)
for (i = 0; i < n; i++)
    for (j = 0; j < i; j++) count++;

// Loop that doubles: i = 1,2,4,8,...,n → runs log2(n) times → O(log n)
for (i = 1; i < n; i = i * 2) count++;

// Two loops one after another: n + n = 2n → O(n)
for (i = 0; i < n; i++) x++;
for (j = 0; j < n; j++) y++;

Rules: nested loops multiply, loops written one after another add. Then keep only the biggest term and drop constants.

নিয়ম: nested loop হলে গুণ হয়, পরপর লেখা loop হলে যোগ হয়। তারপর সবচেয়ে বড় term রেখে constant বাদ দিন।

Example: Find the complexity of this code.
for (i = 0; i < n; i++)          // n times
    for (j = 1; j < n; j = j*2)  // log n times
        printf("*");
Outer loop runs \( n \) times. Inner loop runs \( \log_2 n \) times for each outer step. Total = \( n \times \log n \). Answer: \( O(n \log n) \).
Example: এই code-এর complexity বের করুন।
for (i = 0; i < n; i++)          // n বার
    for (j = 1; j < n; j = j*2)  // log n বার
        printf("*");
Outer loop চলে \( n \) বার। প্রতিটা outer step-এ inner loop চলে \( \log_2 n \) বার। মোট = \( n \times \log n \)। উত্তর: \( O(n \log n) \)।

Complexity of recursion: recurrence relationsRecursion-এর complexity: recurrence relation

For a recursive function, we write a recurrence. Example: merge sort splits the array into 2 halves, solves both, then merges in \( n \) steps:

Recursive function-এর জন্য আমরা একটা recurrence লিখি। Example: merge sort array-কে 2 ভাগে ভাগ করে, দুটোই solve করে, তারপর \( n \) step-এ merge করে:

\[ T(n) = 2T(n/2) + n \quad \Rightarrow \quad T(n) = O(n \log n) \]

Master theorem (simple cases)Master theorem (সহজ case)

For recurrences of the form \( T(n) = a\,T(n/b) + n^d \) (with \( a \ge 1, b > 1 \)), compare \( d \) with \( \log_b a \):

  • If \( d > \log_b a \): \( T(n) = O(n^d) \) — the outside work wins.
  • If \( d = \log_b a \): \( T(n) = O(n^d \log n) \) — both are equal, we get an extra log.
  • If \( d < \log_b a \): \( T(n) = O(n^{\log_b a}) \) — the recursive calls win.

\( T(n) = a\,T(n/b) + n^d \) form-এর recurrence-এর জন্য (\( a \ge 1, b > 1 \)), \( d \)-কে \( \log_b a \)-এর সাথে compare করুন:

  • যদি \( d > \log_b a \): \( T(n) = O(n^d) \) — বাইরের কাজটাই বড়।
  • যদি \( d = \log_b a \): \( T(n) = O(n^d \log n) \) — দুটো সমান, একটা extra log আসে।
  • যদি \( d < \log_b a \): \( T(n) = O(n^{\log_b a}) \) — recursive call-গুলোই বড়।
Example: Solve these with the Master theorem.
  • \( T(n) = 2T(n/2) + n \): here \( a=2, b=2, d=1 \). \( \log_2 2 = 1 = d \). Case 2 → \( O(n \log n) \). (Merge sort)
  • \( T(n) = T(n/2) + 1 \): here \( a=1, b=2, d=0 \). \( \log_2 1 = 0 = d \). Case 2 → \( O(\log n) \). (Binary search)
  • \( T(n) = 4T(n/2) + n \): here \( a=4, b=2, d=1 \). \( \log_2 4 = 2 > 1 \). Case 3 → \( O(n^2) \).
  • \( T(n) = 2T(n/2) + n^2 \): \( \log_2 2 = 1 < 2 \). Case 1 → \( O(n^2) \).
Example: Master theorem দিয়ে solve করুন।
  • \( T(n) = 2T(n/2) + n \): এখানে \( a=2, b=2, d=1 \)। \( \log_2 2 = 1 = d \)। Case 2 → \( O(n \log n) \)। (Merge sort)
  • \( T(n) = T(n/2) + 1 \): এখানে \( a=1, b=2, d=0 \)। \( \log_2 1 = 0 = d \)। Case 2 → \( O(\log n) \)। (Binary search)
  • \( T(n) = 4T(n/2) + n \): এখানে \( a=4, b=2, d=1 \)। \( \log_2 4 = 2 > 1 \)। Case 3 → \( O(n^2) \)।
  • \( T(n) = 2T(n/2) + n^2 \): \( \log_2 2 = 1 < 2 \)। Case 1 → \( O(n^2) \)।
Note (exam tip): BUET loves "find the time complexity of this code" questions. Method: count how many times the innermost statement runs. Also remember: Big-O drops constants, so \( O(2n) = O(n) \) and \( O(n^2 + n) = O(n^2) \).
Note (exam tip): BUET-এ "এই code-এর time complexity বের করুন" ধরনের প্রশ্ন খুব আসে। পদ্ধতি: সবচেয়ে ভিতরের statement কতবার চলে তা গুনুন। মনে রাখুন: Big-O constant বাদ দেয়, তাই \( O(2n) = O(n) \) আর \( O(n^2 + n) = O(n^2) \)।

Inferring step counts from scaling (Real Exam!)Scaling থেকে step count বের করা (Real Exam!)

Sometimes the exam does not give you the formula. It gives you one data point (how many steps for one input size) and expects you to find the steps for any other size. The trick: if an algorithm is \( \Theta(n^k) \), then steps \( = c \cdot n^k \) for some constant \( c \). The constant cancels when you take a ratio:

অনেক সময় পরীক্ষায় formula দেওয়া থাকে না। শুধু একটা data point দেওয়া থাকে (একটা input size-এর জন্য কত step লাগে), আর অন্য যেকোনো size-এর জন্য step বের করতে বলে। কৌশল: algorithm যদি \( \Theta(n^k) \) হয়, তাহলে step \( = c \cdot n^k \), যেখানে \( c \) একটা constant। Ratio নিলে constant-টা কেটে যায়:

\[ \frac{\text{steps}(n)}{\text{steps}(m)} = \frac{c \cdot n^k}{c \cdot m^k} = \left(\frac{n}{m}\right)^{k} \quad \Rightarrow \quad \text{steps}(n) = \text{steps}(m)\cdot\left(\frac{n}{m}\right)^{k} \]
Example (real exam style): A matrix-multiplication algorithm needs 21 steps for a 7×7 multiplication. How many steps does it need for an n×n multiplication?
  1. Standard matrix multiplication is \( \Theta(n^3) \), so steps \( = c \cdot n^3 \).
  2. Use the given data point to find \( c \): \( 21 = c \cdot 7^3 = 343c \Rightarrow c = \frac{21}{343} = \frac{3}{49} \).
  3. So: \( \text{steps}(n) = 21 \cdot \left(\frac{n}{7}\right)^3 = \frac{21 n^3}{343} = \frac{3n^3}{49} \).
  4. Check with \( n = 14 \): \( \frac{3 \cdot 14^3}{49} = \frac{3 \cdot 2744}{49} = 168 \). Doubling \( n \) in an \( n^3 \) algorithm should multiply steps by \( 2^3 = 8 \), and indeed \( 21 \times 8 = 168 \). It matches.
Example (real exam style): একটা matrix-multiplication algorithm-এর 7×7 multiplication-এ 21 step লাগে। n×n multiplication-এ কত step লাগবে?
  1. Standard matrix multiplication হলো \( \Theta(n^3) \), তাই step \( = c \cdot n^3 \)।
  2. দেওয়া data point থেকে \( c \) বের করুন: \( 21 = c \cdot 7^3 = 343c \Rightarrow c = \frac{21}{343} = \frac{3}{49} \)।
  3. তাহলে: \( \text{steps}(n) = 21 \cdot \left(\frac{n}{7}\right)^3 = \frac{21 n^3}{343} = \frac{3n^3}{49} \)।
  4. \( n = 14 \) দিয়ে check করুন: \( \frac{3 \cdot 14^3}{49} = \frac{3 \cdot 2744}{49} = 168 \)। \( n^3 \) algorithm-এ \( n \) double করলে step \( 2^3 = 8 \) গুণ হওয়ার কথা, আর সত্যিই \( 21 \times 8 = 168 \)। মিলে গেছে।
Note (real exam): This exact question ("21 steps for 7×7 — how many for n×n?") was asked in October 2017. Remember the two-step recipe: (1) know the power \( k \) from the algorithm, (2) find the constant from the given data point.
Note (real exam): ঠিক এই প্রশ্নটা ("7×7-এ 21 step — n×n-এ কত?") October 2017-তে এসেছিল। দুই-step recipe মনে রাখুন: (1) algorithm থেকে power \( k \) জানুন, (2) দেওয়া data point থেকে constant বের করুন।

2. Sorting2. Sorting

Sorting means arranging items in order (small to big, or big to small). It is the most classic algorithm topic. For each sort, learn: how it works, one full trace, and its complexity.

Sorting মানে item-গুলো order-এ সাজানো (ছোট থেকে বড়, বা বড় থেকে ছোট)। এটা সবচেয়ে classic algorithm topic। প্রতিটা sort-এর জন্য শিখুন: কীভাবে কাজ করে, একটা full trace, আর তার complexity।

Bubble sortBubble sort

Compare each pair of neighbours. If they are in the wrong order, swap them. After one full pass, the biggest element "bubbles" to the end. Repeat \( n-1 \) passes.

পাশাপাশি প্রতিটা pair compare করুন। ভুল order-এ থাকলে swap করুন। এক pass শেষে সবচেয়ে বড় element শেষে "bubble" হয়ে চলে যায়। এভাবে \( n-1 \) pass repeat করুন।

void bubbleSort(int a[], int n) {
    for (int i = 0; i < n - 1; i++)
        for (int j = 0; j < n - 1 - i; j++)
            if (a[j] > a[j+1]) {          // wrong order?
                int t = a[j]; a[j] = a[j+1]; a[j+1] = t;  // swap
            }
}
Example (trace): Sort [5, 1, 4, 2] with bubble sort.
  • Pass 1: (5,1)→swap → [1,5,4,2]; (5,4)→swap → [1,4,5,2]; (5,2)→swap → [1,4,2,5]. Biggest (5) is at the end.
  • Pass 2: (1,4)→ok; (4,2)→swap → [1,2,4,5].
  • Pass 3: (1,2)→ok. Sorted: [1,2,4,5].
Example (trace): Bubble sort দিয়ে [5, 1, 4, 2] sort করুন।
  • Pass 1: (5,1)→swap → [1,5,4,2]; (5,4)→swap → [1,4,5,2]; (5,2)→swap → [1,4,2,5]। সবচেয়ে বড় (5) শেষে চলে গেল।
  • Pass 2: (1,4)→ঠিক আছে; (4,2)→swap → [1,2,4,5]।
  • Pass 3: (1,2)→ঠিক আছে। Sorted: [1,2,4,5]।

Selection sortSelection sort

In pass \( i \), find the smallest element in the unsorted part and swap it into position \( i \). It always does about \( n^2/2 \) comparisons, but at most \( n-1 \) swaps (good when swaps are costly).

Pass \( i \)-তে unsorted অংশের সবচেয়ে ছোট element খুঁজে position \( i \)-তে swap করুন। এটা সবসময় প্রায় \( n^2/2 \) comparison করে, কিন্তু সর্বোচ্চ \( n-1 \) swap (swap costly হলে ভালো)।

void selectionSort(int a[], int n) {
    for (int i = 0; i < n - 1; i++) {
        int min = i;
        for (int j = i + 1; j < n; j++)
            if (a[j] < a[min]) min = j;
        int t = a[i]; a[i] = a[min]; a[min] = t;
    }
}
Example (trace): Sort [29, 10, 14, 37, 13].
  • Pass 1: min = 10 → swap with 29 → [10, 29, 14, 37, 13]
  • Pass 2: min = 13 → swap with 29 → [10, 13, 14, 37, 29]
  • Pass 3: min = 14 → already in place → [10, 13, 14, 37, 29]
  • Pass 4: min = 29 → swap with 37 → [10, 13, 14, 29, 37]. Done.
Example (trace): [29, 10, 14, 37, 13] sort করুন।
  • Pass 1: min = 10 → 29-এর সাথে swap → [10, 29, 14, 37, 13]
  • Pass 2: min = 13 → 29-এর সাথে swap → [10, 13, 14, 37, 29]
  • Pass 3: min = 14 → আগেই ঠিক জায়গায় → [10, 13, 14, 37, 29]
  • Pass 4: min = 29 → 37-এর সাথে swap → [10, 13, 14, 29, 37]। শেষ।

Insertion sortInsertion sort

Like sorting playing cards in your hand. Take the next element and insert it into its correct place inside the already-sorted left part. Very fast (\( O(n) \)) if the array is almost sorted.

হাতে তাসের card সাজানোর মতো। পরের element নিন আর বাম দিকের already-sorted অংশে তার সঠিক জায়গায় insert করুন। Array প্রায় sorted থাকলে খুব fast (\( O(n) \))।

void insertionSort(int a[], int n) {
    for (int i = 1; i < n; i++) {
        int key = a[i], j = i - 1;
        while (j >= 0 && a[j] > key) {   // shift bigger ones right
            a[j+1] = a[j]; j--;
        }
        a[j+1] = key;                       // insert
    }
}
Example (trace): Sort [7, 3, 5, 2].
  • key=3: 7>3 shift → [7,7,5,2] → insert 3 → [3, 7, 5, 2]
  • key=5: 7>5 shift → insert 5 → [3, 5, 7, 2]
  • key=2: 7,5,3 all shift → insert 2 → [2, 3, 5, 7]. Done.
Example (trace): [7, 3, 5, 2] sort করুন।
  • key=3: 7>3 shift → তারপর 3 insert → [3, 7, 5, 2]
  • key=5: 7>5 shift → 5 insert → [3, 5, 7, 2]
  • key=2: 7, 5, 3 সব shift → 2 insert → [2, 3, 5, 7]। শেষ।

Merge sort (divide and conquer)Merge sort (divide and conquer)

Idea: split the array into two halves, sort each half (recursively), then merge the two sorted halves into one sorted array. Merging two sorted lists is easy: repeatedly take the smaller front element.

Idea: array-কে দুই ভাগে ভাগ করুন, প্রতিটা ভাগ (recursively) sort করুন, তারপর দুটো sorted অংশকে একটাতে merge করুন। দুটো sorted list merge করা সহজ: বারবার সামনের ছোট element-টা নিন।

void merge(int a[], int l, int m, int r) {
    int n1 = m - l + 1, n2 = r - m;
    int L[n1], R[n2];
    for (int i = 0; i < n1; i++) L[i] = a[l + i];
    for (int j = 0; j < n2; j++) R[j] = a[m + 1 + j];
    int i = 0, j = 0, k = l;
    while (i < n1 && j < n2)
        a[k++] = (L[i] <= R[j]) ? L[i++] : R[j++];
    while (i < n1) a[k++] = L[i++];
    while (j < n2) a[k++] = R[j++];
}
void mergeSort(int a[], int l, int r) {
    if (l >= r) return;              // 1 element = already sorted
    int m = (l + r) / 2;
    mergeSort(a, l, m);
    mergeSort(a, m + 1, r);
    merge(a, l, m, r);
}
Divide 38 27 43 3 9 82 10 38 27 43 3 9 82 10 38 27 43 3 9 82 10 Merge 27 38 3 43 9 82 10 3 27 38 43 9 10 82 3 9 10 27 38 43 82
Merge sort on [38, 27, 43, 3, 9, 82, 10]: divide down to single elements, then merge sorted parts back up.[38, 27, 43, 3, 9, 82, 10]-এ merge sort: একদম single element পর্যন্ত divide করুন, তারপর sorted অংশগুলো merge করে উপরে উঠুন।
Example (merge step in detail): Merge [3, 27] and [9, 10]. Compare fronts: 3 vs 9 → take 3. Then 27 vs 9 → take 9. Then 27 vs 10 → take 10. Left list [27] remains → copy it. Result: [3, 9, 10, 27]. Each element is touched once, so merging is \( O(n) \).
Example (merge step বিস্তারিত): [3, 27] আর [9, 10] merge করুন। সামনের দুটো compare: 3 vs 9 → 3 নিন। তারপর 27 vs 9 → 9 নিন। তারপর 27 vs 10 → 10 নিন। বাম list-এ [27] বাকি → copy করুন। Result: [3, 9, 10, 27]। প্রতিটা element একবারই ধরা হয়, তাই merge \( O(n) \)।
\[ T(n) = 2T(n/2) + n = O(n \log n) \quad \text{(best = average = worst)} \]

Quick sort and partitionQuick sort আর partition

Idea: pick one element as the pivot. Rearrange (partition) the array so that everything smaller than the pivot goes left, everything bigger goes right. Now the pivot is in its final place. Recursively quick sort the left part and the right part.

Idea: একটা element-কে pivot ধরুন। Array-টা এমনভাবে rearrange (partition) করুন যেন pivot-এর চেয়ে ছোট সব বামে যায়, বড় সব ডানে যায়। এখন pivot তার final জায়গায়। তারপর বাম অংশ আর ডান অংশে recursively quick sort করুন।

int partition(int a[], int lo, int hi) {   // Lomuto: pivot = last element
    int pivot = a[hi];
    int i = lo - 1;                        // end of "smaller" zone
    for (int j = lo; j < hi; j++)
        if (a[j] < pivot) {
            i++;
            int t = a[i]; a[i] = a[j]; a[j] = t;
        }
    int t = a[i+1]; a[i+1] = a[hi]; a[hi] = t;  // place pivot
    return i + 1;                          // pivot's final index
}
void quickSort(int a[], int lo, int hi) {
    if (lo < hi) {
        int p = partition(a, lo, hi);
        quickSort(a, lo, p - 1);
        quickSort(a, p + 1, hi);
    }
}
Example (partition trace): Array [7, 2, 8, 1, 5], pivot = 5 (last). Start i = -1.
  • j=0: 7 < 5? No.
  • j=1: 2 < 5? Yes → i=0, swap a[0],a[1] → [2, 7, 8, 1, 5]
  • j=2: 8 < 5? No.
  • j=3: 1 < 5? Yes → i=1, swap a[1],a[3] → [2, 1, 8, 7, 5]
  • End: swap pivot into i+1=2 → [2, 1, 5, 7, 8]. Pivot 5 is fixed at index 2. Left part [2,1], right part [7,8] are sorted recursively.
Example (partition trace): Array [7, 2, 8, 1, 5], pivot = 5 (শেষেরটা)। শুরুতে i = -1।
  • j=0: 7 < 5? না।
  • j=1: 2 < 5? হ্যাঁ → i=0, a[0],a[1] swap → [2, 7, 8, 1, 5]
  • j=2: 8 < 5? না।
  • j=3: 1 < 5? হ্যাঁ → i=1, a[1],a[3] swap → [2, 1, 8, 7, 5]
  • শেষে: pivot-কে i+1=2 জায়গায় swap → [2, 1, 5, 7, 8]। Pivot 5 index 2-তে fix হলো। বাম [2,1] আর ডান [7,8] recursively sort হবে।
Quick sort animation
Quick sort in action. Red line = pivot. Smaller values move left of it, bigger values move right.Quick sort চলছে। লাল দাগ = pivot। ছোট value-গুলো এর বামে যায়, বড়গুলো ডানে।
Note (exam favourite): Quick sort worst case is \( O(n^2) \). It happens when the pivot is always the smallest or biggest element — for example, an already sorted array with last-element pivot. Then partition splits into sizes \( n-1 \) and \( 0 \), giving \( T(n) = T(n-1) + n = O(n^2) \). Average case is \( O(n \log n) \). Random pivot avoids the bad case in practice.
Note (exam favourite): Quick sort-এর worst case \( O(n^2) \)। এটা হয় যখন pivot সবসময় সবচেয়ে ছোট বা বড় element হয় — যেমন last-element pivot দিয়ে already sorted array। তখন partition ভাগ করে \( n-1 \) আর \( 0 \) size-এ, ফলে \( T(n) = T(n-1) + n = O(n^2) \)। Average case \( O(n \log n) \)। Random pivot নিলে practically খারাপ case এড়ানো যায়।

Counting sort (no comparisons!)Counting sort (কোনো comparison নেই!)

If all values are small integers in range 0..k, we can sort without comparing. Count how many times each value appears, then rebuild the array. Time \( O(n + k) \). Example: [4, 2, 2, 8, 3] → counts: 2 appears 2 times, 3 once, 4 once, 8 once → output [2, 2, 3, 4, 8]. It needs extra memory for the count array, and only works for limited integer ranges.

সব value যদি 0..k range-এর ছোট integer হয়, compare না করেই sort করা যায়। প্রতিটা value কতবার আছে count করুন, তারপর array আবার বানান। Time \( O(n + k) \)। Example: [4, 2, 2, 8, 3] → count: 2 আছে 2 বার, 3 একবার, 4 একবার, 8 একবার → output [2, 2, 3, 4, 8]। এটার জন্য count array-এর extra memory লাগে, আর শুধু limited integer range-এ কাজ করে।

StabilityStability

A sort is stable if equal elements keep their original order. Example: sort students by marks; two students both have 80. A stable sort keeps them in their original order. Bubble, insertion, merge, counting sort are stable. Selection and quick sort (typical versions) are not stable.

একটা sort stable যদি সমান element-গুলো তাদের আগের order-এই থাকে। Example: marks দিয়ে student sort করা হলো; দুইজনের marks-ই 80। Stable sort তাদের আগের order-এ রাখে। Bubble, insertion, merge, counting sort — stable। Selection আর quick sort (সাধারণ version) stable না

Comparison table (memorize this)Comparison table (এটা মুখস্থ করুন)

AlgorithmAlgorithmBestAverageWorstSpaceStable?Stable?
Bubble sort\( O(n) \)\( O(n^2) \)\( O(n^2) \)\( O(1) \)Yesহ্যাঁ
Selection sort\( O(n^2) \)\( O(n^2) \)\( O(n^2) \)\( O(1) \)Noনা
Insertion sort\( O(n) \)\( O(n^2) \)\( O(n^2) \)\( O(1) \)Yesহ্যাঁ
Merge sort\( O(n \log n) \)\( O(n \log n) \)\( O(n \log n) \)\( O(n) \)Yesহ্যাঁ
Quick sort\( O(n \log n) \)\( O(n \log n) \)\( O(n^2) \)\( O(\log n) \)Noনা
Heap sort\( O(n \log n) \)\( O(n \log n) \)\( O(n \log n) \)\( O(1) \)Noনা
Counting sort\( O(n+k) \)\( O(n+k) \)\( O(n+k) \)\( O(k) \)Yesহ্যাঁ
Note: Bubble sort best case \( O(n) \) needs the "no swap in a pass → stop" optimization. Any comparison-based sort needs at least \( \Omega(n \log n) \) in the worst case — this is a famous lower bound.
Note: Bubble sort-এর best case \( O(n) \) পেতে "এক pass-এ কোনো swap নেই → থেমে যাও" optimization লাগে। যেকোনো comparison-based sort-এর worst case-এ কমপক্ষে \( \Omega(n \log n) \) লাগে — এটা একটা বিখ্যাত lower bound।

3. Searching3. Searching

Linear searchLinear search

Check elements one by one from the start until you find the target. Works on any array (sorted or not). Worst case: check all \( n \) elements → \( O(n) \). Average: about \( n/2 \) checks.

শুরু থেকে একটা একটা করে element check করুন, target পাওয়া পর্যন্ত। যেকোনো array-তে কাজ করে (sorted হোক বা না হোক)। Worst case: সব \( n \)-টা element check → \( O(n) \)। Average: প্রায় \( n/2 \) check।

int linearSearch(int a[], int n, int key) {
    for (int i = 0; i < n; i++)
        if (a[i] == key) return i;   // found
    return -1;                       // not found
}

Binary searchBinary search

Only works on a sorted array. Look at the middle element. If it is the target, done. If the target is smaller, throw away the right half. If bigger, throw away the left half. Every step cuts the search space in half.

শুধু sorted array-তে কাজ করে। মাঝের element দেখুন। এটাই target হলে শেষ। Target ছোট হলে ডান অর্ধেক বাদ দিন। বড় হলে বাম অর্ধেক বাদ দিন। প্রতিটা step-এ search space অর্ধেক হয়ে যায়।

int binarySearch(int a[], int n, int key) {
    int lo = 0, hi = n - 1;
    while (lo <= hi) {
        int mid = lo + (hi - lo) / 2;   // safe from overflow
        if (a[mid] == key) return mid;
        else if (a[mid] < key) lo = mid + 1;  // go right
        else hi = mid - 1;                     // go left
    }
    return -1;
}

Why \( O(\log n) \)? Each step halves the range: \( n \to n/2 \to n/4 \to \dots \to 1 \). If it takes \( k \) steps, then \( n/2^k = 1 \), so \( 2^k = n \), so \( k = \log_2 n \). For n = 1,000,000 only about 20 steps!

কেন \( O(\log n) \)? প্রতিটা step-এ range অর্ধেক হয়: \( n \to n/2 \to n/4 \to \dots \to 1 \)। যদি \( k \) step লাগে, তাহলে \( n/2^k = 1 \), মানে \( 2^k = n \), মানে \( k = \log_2 n \)। n = 1,000,000 হলে মাত্র প্রায় 20 step!

Binary search depiction
Binary search: each comparison removes half of the remaining elements.Binary search: প্রতিটা comparison বাকি element-গুলোর অর্ধেক বাদ দিয়ে দেয়।
Example (trace): Find 23 in [4, 8, 15, 16, 23, 42, 50].
  • lo=0, hi=6 → mid=3, a[3]=16. 16 < 23 → lo=4.
  • lo=4, hi=6 → mid=5, a[5]=42. 42 > 23 → hi=4.
  • lo=4, hi=4 → mid=4, a[4]=23. Found at index 4. Total: 3 comparisons for 7 elements (\( \lceil \log_2 7 \rceil = 3 \)).
Example (trace): [4, 8, 15, 16, 23, 42, 50]-এ 23 খুঁজুন।
  • lo=0, hi=6 → mid=3, a[3]=16। 16 < 23 → lo=4।
  • lo=4, hi=6 → mid=5, a[5]=42। 42 > 23 → hi=4।
  • lo=4, hi=4 → mid=4, a[4]=23। Index 4-এ পাওয়া গেল। মোট: 7 element-এর জন্য 3 comparison (\( \lceil \log_2 7 \rceil = 3 \))।
Note (common pitfalls):
  • Array must be sorted — binary search on unsorted data gives wrong answers.
  • Use lo <= hi, not lo < hi, or you may miss the last element.
  • mid = (lo + hi) / 2 can overflow for big values; write lo + (hi - lo) / 2.
  • After comparing, move to mid + 1 or mid - 1. Keeping mid can cause an infinite loop.
Note (common pitfalls):
  • Array অবশ্যই sorted হতে হবে — unsorted data-তে binary search ভুল উত্তর দেয়।
  • lo <= hi ব্যবহার করুন, lo < hi না, নাহলে শেষ element miss হতে পারে।
  • বড় value-তে mid = (lo + hi) / 2 overflow করতে পারে; লিখুন lo + (hi - lo) / 2
  • Compare-এর পর mid + 1 বা mid - 1-এ যান। mid রেখে দিলে infinite loop হতে পারে।
Linear searchLinear searchBinary searchBinary search
Needs sorted data?Sorted data লাগে?NoনাYesহ্যাঁ
Worst timeWorst time\( O(n) \)\( O(\log n) \)
Best timeBest time\( O(1) \)\( O(1) \)
Works on linked list?Linked list-এ কাজ করে?Yesহ্যাঁNot efficiently (no random access)ভালোভাবে না (random access নেই)

4. Divide and Conquer4. Divide and Conquer

Divide and conquer is a 3-step pattern:

  1. Divide: break the problem into smaller subproblems of the same type.
  2. Conquer: solve each subproblem recursively (tiny ones directly).
  3. Combine: join the small answers into the full answer.

You already know two examples: merge sort (divide into halves, sort, merge) and binary search (divide, keep only one half, no combine needed). Quick sort is also divide and conquer (the work happens in the partition, before recursion).

Divide and conquer হলো 3-step pattern:

  1. Divide: problem-টাকে একই ধরনের ছোট ছোট subproblem-এ ভাঙুন।
  2. Conquer: প্রতিটা subproblem recursively solve করুন (খুব ছোট হলে সরাসরি)।
  3. Combine: ছোট উত্তরগুলো জোড়া দিয়ে পুরো উত্তর বানান।

দুটো example আপনি আগেই জানেন: merge sort (অর্ধেক করুন, sort করুন, merge করুন) আর binary search (divide করে শুধু এক অর্ধেক রাখুন, combine লাগে না)। Quick sort-ও divide and conquer (কাজটা হয় partition-এ, recursion-এর আগে)।

Example: fast power — power(x, n)Example: fast power — power(x, n)

Naive way: multiply \( x \) by itself \( n \) times → \( O(n) \). Divide and conquer way: \( x^n = (x^{n/2})^2 \) if \( n \) is even, and \( x \cdot (x^{(n-1)/2})^2 \) if odd. Only one recursive call!

সাধারণ উপায়: \( x \)-কে \( n \) বার গুণ → \( O(n) \)। Divide and conquer উপায়: \( n \) even হলে \( x^n = (x^{n/2})^2 \), odd হলে \( x \cdot (x^{(n-1)/2})^2 \)। মাত্র একটা recursive call!

long long power(long long x, int n) {
    if (n == 0) return 1;
    long long half = power(x, n / 2);
    if (n % 2 == 0) return half * half;
    else            return x * half * half;
}
\[ T(n) = T(n/2) + O(1) \Rightarrow T(n) = O(\log n) \]
Example (trace): power(2, 10) → needs power(2, 5) → needs power(2, 2) → needs power(2, 1) → needs power(2, 0) = 1. Going back up: power(2,1) = 2·1·1 = 2; power(2,2) = 2·2 = 4; power(2,5) = 2·4·4 = 32; power(2,10) = 32·32 = 1024. Only 4 multiplied levels instead of 10 multiplications.
Example (trace): power(2, 10) → লাগে power(2, 5) → লাগে power(2, 2) → লাগে power(2, 1) → লাগে power(2, 0) = 1। ফেরার পথে: power(2,1) = 2·1·1 = 2; power(2,2) = 2·2 = 4; power(2,5) = 2·4·4 = 32; power(2,10) = 32·32 = 1024। 10 বার গুণের বদলে মাত্র 4 level।

Example: max and min of an arrayExample: array-এর max আর min

Split the array in half, find (max, min) of each half recursively, then combine with 2 comparisons: overall max = max of two maxes, overall min = min of two mins. Recurrence: \( T(n) = 2T(n/2) + 2 \). This gives about \( 3n/2 - 2 \) comparisons, better than the naive \( 2n - 2 \) (comparing every element with both current max and min).

Array অর্ধেক করুন, প্রতিটা অর্ধেকের (max, min) recursively বের করুন, তারপর 2টা comparison দিয়ে combine করুন: মোট max = দুই max-এর বড়টা, মোট min = দুই min-এর ছোটটা। Recurrence: \( T(n) = 2T(n/2) + 2 \)। এতে প্রায় \( 3n/2 - 2 \) comparison লাগে, যা সাধারণ \( 2n - 2 \)-এর (প্রতিটা element-কে max আর min দুটোর সাথেই compare) চেয়ে ভালো।

Note (exam tip): When a question says "design a divide and conquer algorithm", always write 3 things: how you divide, the recurrence \( T(n) \), and the final complexity (solve with Master theorem).
Note (exam tip): প্রশ্নে "divide and conquer algorithm design করুন" বললে সবসময় 3টা জিনিস লিখুন: কীভাবে divide করছেন, recurrence \( T(n) \), আর final complexity (Master theorem দিয়ে solve)।

5. Greedy Algorithms5. Greedy Algorithms

A greedy algorithm builds the answer step by step. At every step it takes the choice that looks best right now, and never goes back to change it. Greedy is simple and fast — but it only gives the correct answer for problems with a special structure (the "greedy choice" must be safe). For many problems, greedy is wrong.

Greedy algorithm উত্তরটা ধাপে ধাপে বানায়। প্রতিটা step-এ এই মুহূর্তে যেটা best দেখায় সেটাই নেয়, আর কখনো পিছনে গিয়ে বদলায় না। Greedy সহজ আর fast — কিন্তু শুধু বিশেষ structure-ওয়ালা problem-এ সঠিক উত্তর দেয় ("greedy choice" safe হতে হয়)। অনেক problem-এ greedy ভুল।

Activity selectionActivity selection

Problem: you have activities with start and finish times. One person can do one activity at a time. Pick the maximum number of non-overlapping activities. Greedy rule: sort by finish time, always take the activity that finishes earliest and does not clash with the last taken one.

Problem: কিছু activity আছে, প্রতিটার start আর finish time দেওয়া। একজন একসাথে একটাই activity করতে পারে। সর্বোচ্চ সংখ্যক non-overlapping activity বাছুন। Greedy rule: finish time দিয়ে sort করুন, সবসময় সেই activity নিন যেটা সবচেয়ে আগে শেষ হয় আর শেষ নেওয়াটার সাথে clash করে না।

Example: Activities (start, finish): A(1,4), B(3,5), C(0,6), D(5,7), E(3,8), F(6,10), G(8,11).
  • Sorted by finish: A(1,4), B(3,5), C(0,6), D(5,7), E(3,8), F(6,10), G(8,11).
  • Take A (finishes first, at 4).
  • B starts at 3 < 4 → clash, skip. C starts at 0 → clash, skip.
  • D starts at 5 ≥ 4 → take D (finishes at 7).
  • E starts 3 → skip. F starts 6 < 7 → skip.
  • G starts 8 ≥ 7 → take G.
Answer: {A, D, G}, 3 activities. Complexity: \( O(n \log n) \) for sorting.
Example: Activity (start, finish): A(1,4), B(3,5), C(0,6), D(5,7), E(3,8), F(6,10), G(8,11)।
  • Finish দিয়ে sorted: A(1,4), B(3,5), C(0,6), D(5,7), E(3,8), F(6,10), G(8,11)।
  • A নিন (সবার আগে শেষ, 4-এ)।
  • B শুরু 3 < 4 → clash, বাদ। C শুরু 0 → clash, বাদ।
  • D শুরু 5 ≥ 4 → D নিন (শেষ 7-এ)।
  • E শুরু 3 → বাদ। F শুরু 6 < 7 → বাদ।
  • G শুরু 8 ≥ 7 → G নিন।
উত্তর: {A, D, G}, 3টা activity। Complexity: sorting-এর জন্য \( O(n \log n) \)।

Coin change: where greedy works and where it failsCoin change: greedy কোথায় কাজ করে, কোথায় fail করে

Problem: make an amount with the fewest coins. Greedy rule: always take the biggest coin that fits.

  • Works: coins {25, 10, 5, 1}, amount 63 → 25+25+10+1+1+1 = 6 coins. This is optimal (this coin system is "canonical").
  • Fails: coins {1, 3, 4}, amount 6. Greedy: 4+1+1 = 3 coins. Optimal: 3+3 = 2 coins. Greedy got it wrong!

Lesson: greedy needs proof. When greedy fails, we use dynamic programming (next section).

Problem: সবচেয়ে কম coin দিয়ে একটা amount বানান। Greedy rule: সবসময় সবচেয়ে বড় coin নিন যেটা fit করে।

  • কাজ করে: coin {25, 10, 5, 1}, amount 63 → 25+25+10+1+1+1 = 6 coin। এটাই optimal (এই coin system "canonical")।
  • Fail করে: coin {1, 3, 4}, amount 6। Greedy: 4+1+1 = 3 coin। Optimal: 3+3 = 2 coin। Greedy ভুল করলো!

শিক্ষা: greedy-র জন্য proof লাগে। Greedy fail করলে আমরা dynamic programming ব্যবহার করি (পরের section)।

Huffman codingHuffman coding

Goal: compress text by giving short binary codes to frequent characters and longer codes to rare ones. Method: make each character a tree node with its frequency. Repeat: take the two nodes with the smallest frequencies, join them under a new parent whose frequency is their sum. Stop when one tree remains. Left edge = 0, right edge = 1. Each character's code = path from root. No code is a prefix of another (prefix-free), so decoding is unambiguous.

লক্ষ্য: বেশি frequent character-কে ছোট binary code আর কম frequent-কে লম্বা code দিয়ে text compress করা। পদ্ধতি: প্রতিটা character-কে frequency-সহ একটা tree node বানান। বারবার করুন: সবচেয়ে ছোট frequency-র দুটো node নিন, একটা নতুন parent-এর নিচে জোড়া দিন যার frequency হলো দুটোর যোগফল। একটা tree বাকি থাকলে থামুন। বাম edge = 0, ডান edge = 1। প্রতিটা character-এর code = root থেকে path। কোনো code অন্য code-এর prefix না (prefix-free), তাই decode করা unambiguous।

Example (full build): Frequencies: a:5, b:9, c:12, d:13, e:16, f:45.
  1. Merge two smallest: a(5)+b(9) = node(14). Nodes: {12, 13, 14, 16, 45}
  2. Merge c(12)+d(13) = node(25). Nodes: {14, 16, 25, 45}
  3. Merge node14 + e(16) = node(30). Nodes: {25, 30, 45}
  4. Merge node25 + node30 = node(55). Nodes: {45, 55}
  5. Merge f(45) + node55 = root(100).
Codes (left=0, right=1): f = 0, c = 100, d = 101, a = 1100, b = 1101, e = 111. Frequent f got 1 bit; rare a, b got 4 bits. Total bits = 45·1 + 12·3 + 13·3 + 5·4 + 9·4 + 16·3 = 224, much less than 3 bits each (100·3 = 300).
Example (পুরো build): Frequency: a:5, b:9, c:12, d:13, e:16, f:45।
  1. সবচেয়ে ছোট দুটো merge: a(5)+b(9) = node(14)। Node: {12, 13, 14, 16, 45}
  2. c(12)+d(13) merge = node(25)। Node: {14, 16, 25, 45}
  3. node14 + e(16) merge = node(30)। Node: {25, 30, 45}
  4. node25 + node30 merge = node(55)। Node: {45, 55}
  5. f(45) + node55 merge = root(100)।
Code (বাম=0, ডান=1): f = 0, c = 100, d = 101, a = 1100, b = 1101, e = 111। Frequent f পেল 1 bit; rare a, b পেল 4 bit। মোট bit = 45·1 + 12·3 + 13·3 + 5·4 + 9·4 + 16·3 = 224, যা সবাইকে 3 bit দিলে (100·3 = 300) তার চেয়ে অনেক কম।
Huffman tree example
A Huffman tree built from character frequencies. Frequent characters sit near the root, so their codes are short.Character frequency থেকে বানানো একটা Huffman tree। Frequent character-গুলো root-এর কাছে থাকে, তাই তাদের code ছোট।
Note (exam tip): Huffman uses a min-heap (priority queue) → building takes \( O(n \log n) \). Exams often give frequencies and ask you to build the tree and compute total bits or average code length. Always merge the two smallest — and show every merge step.
Note (exam tip): Huffman-এ min-heap (priority queue) ব্যবহার হয় → build করতে \( O(n \log n) \)। পরীক্ষায় প্রায়ই frequency দিয়ে tree বানাতে আর মোট bit বা average code length বের করতে বলে। সবসময় সবচেয়ে ছোট দুটো merge করুন — আর প্রতিটা merge step দেখান।

Bottlenecks / Limitations of Greedy (Real Exam!)Greedy-র Bottleneck / Limitation (Real Exam!)

Greedy is fast and simple, but it has clear weaknesses. The exam asks you to list the bottlenecks (limitations) of the greedy approach. Learn these five points:

  • Local best ≠ global best. Greedy picks what looks best now. A chain of locally best choices can still end at a bad final answer (a local optimum, not the global optimum).
  • No lookahead, no backtracking. Greedy never looks at future consequences, and once a choice is made it never goes back to undo it. One early wrong choice can ruin everything.
  • Works only for special problems. The problem must have the greedy-choice property (a locally best choice is always safe) and optimal substructure (the best answer contains best answers of subproblems). Most problems do not have both.
  • Correctness is hard to prove. Even when greedy is right, proving it (usually by an exchange argument) is tricky. A greedy that "looks right" can silently be wrong.
  • Famous failures. Greedy fails on 0/1 knapsack (taking the best ratio item first can miss the optimal) and on coin change with a general coin system (coins {1, 3, 4}, amount 6: greedy gives 3 coins, optimal is 2). These need DP.

Greedy fast আর সহজ, কিন্তু এর স্পষ্ট কিছু দুর্বলতা আছে। পরীক্ষায় greedy approach-এর bottleneck (limitation) list করতে বলে। এই পাঁচটা point শিখুন:

  • Local best ≠ global best। Greedy এই মুহূর্তে যা best দেখায় তাই নেয়। Locally best choice-এর chain শেষে একটা খারাপ answer-এ গিয়ে থামতে পারে (local optimum, global optimum না)।
  • কোনো lookahead নেই, backtracking নেই। Greedy কখনো ভবিষ্যতের ফলাফল দেখে না, আর একবার choice নিলে ফিরে গিয়ে বদলায় না। শুরুর একটা ভুল choice সব নষ্ট করতে পারে।
  • শুধু বিশেষ problem-এ কাজ করে। Problem-টার greedy-choice property (locally best choice সবসময় safe) আর optimal substructure (best answer-এর ভিতরে subproblem-এর best answer থাকে) — দুটোই থাকতে হয়। বেশিরভাগ problem-এ দুটো একসাথে থাকে না।
  • Correctness প্রমাণ করা কঠিন। Greedy সঠিক হলেও তা প্রমাণ করা (সাধারণত exchange argument দিয়ে) কঠিন। "ঠিক মনে হচ্ছে" এমন greedy চুপচাপ ভুল হতে পারে।
  • বিখ্যাত failure। Greedy fail করে 0/1 knapsack-এ (best ratio-র item আগে নিলে optimal miss হতে পারে) আর general coin system-এর coin change-এ (coin {1, 3, 4}, amount 6: greedy দেয় 3 coin, optimal 2)। এগুলোতে DP লাগে।
Note (real exam): "What are the bottlenecks/limitations of the greedy approach?" was asked in October 2017 AND October 2018 — two years in a row. Write the five points above, and always give one failure example (coin {1,3,4}, amount 6) to earn full marks.
Note (real exam): "Greedy approach-এর bottleneck/limitation কী কী?" প্রশ্নটা October 2017 এবং October 2018 — পরপর দুই বছর এসেছে। উপরের পাঁচটা point লিখুন, আর full marks পেতে সবসময় একটা failure example দিন (coin {1,3,4}, amount 6)।

6. Dynamic Programming (DP)6. Dynamic Programming (DP)

DP solves problems that have two properties: overlapping subproblems (the same small problem appears many times) and optimal substructure (the best answer is built from best answers of subproblems). Trick: solve each subproblem once, save the result, reuse it.

  • Memoization (top-down): write the normal recursion, but store each result in a table. Before computing, check the table first.
  • Tabulation (bottom-up): fill a table from the smallest cases up to the answer, using loops. No recursion.

DP এমন problem solve করে যার দুটো property আছে: overlapping subproblems (একই ছোট problem বারবার আসে) আর optimal substructure (best উত্তরটা subproblem-গুলোর best উত্তর দিয়ে তৈরি হয়)। কৌশল: প্রতিটা subproblem একবারই solve করুন, result save করুন, আবার ব্যবহার করুন।

  • Memoization (top-down): সাধারণ recursion লিখুন, কিন্তু প্রতিটা result একটা table-এ রাখুন। হিসাবের আগে আগে table check করুন।
  • Tabulation (bottom-up): loop দিয়ে সবচেয়ে ছোট case থেকে উত্তর পর্যন্ত table fill করুন। কোনো recursion নেই।

Fibonacci: the classic first exampleFibonacci: classic প্রথম example

// Naive recursion: fib(n) = fib(n-1) + fib(n-2) → O(2^n), very slow
// fib(5) computes fib(3) twice, fib(2) three times... wasted work!

// Memoization (top-down): O(n)
long long memo[100]; // init all to -1
long long fib(int n) {
    if (n <= 1) return n;
    if (memo[n] != -1) return memo[n];   // already solved?
    return memo[n] = fib(n-1) + fib(n-2);
}

// Tabulation (bottom-up): O(n)
long long fibTab(int n) {
    long long dp[100];
    dp[0] = 0; dp[1] = 1;
    for (int i = 2; i <= n; i++) dp[i] = dp[i-1] + dp[i-2];
    return dp[n];
}

Naive recursion is \( O(2^n) \) because the same values are computed again and again. DP computes each fib(i) once → \( O(n) \).

সাধারণ recursion \( O(2^n) \) কারণ একই value বারবার হিসাব হয়। DP প্রতিটা fib(i) একবারই হিসাব করে → \( O(n) \)।

0/1 Knapsack0/1 Knapsack

Problem: a bag holds weight W. Each item has a weight and a value. Take each item fully or not at all (0/1). Maximize total value. DP: let dp[i][w] = best value using the first \( i \) items with capacity \( w \).

Problem: একটা bag-এ W weight ধরে। প্রতিটা item-এর weight আর value আছে। প্রতিটা item পুরোটা নিন বা একদমই না (0/1)। মোট value maximize করুন। DP: dp[i][w] = প্রথম \( i \)-টা item আর capacity \( w \) দিয়ে best value।

\[ dp[i][w] = \max\big(\, dp[i-1][w],\ \ value_i + dp[i-1][w - weight_i] \,\big) \]

First choice = skip item \( i \). Second choice = take it (only if \( weight_i \le w \)).

প্রথম option = item \( i \) বাদ দিন। দ্বিতীয় option = নিন (শুধু যদি \( weight_i \le w \) হয়)।

Example (full DP table): W = 5. Items: I1(weight 1, value 10), I2(weight 3, value 40), I3(weight 4, value 50). Rows = items used, columns = capacity 0..5.
Example (পুরো DP table): W = 5। Item: I1(weight 1, value 10), I2(weight 3, value 40), I3(weight 4, value 50)। Row = কয়টা item ব্যবহার, column = capacity 0..5।
dpw=0w=1w=2w=3w=4w=5
no itemsকোনো item না000000
I1 (1,10)01010101010
+I2 (3,40)01010405050
+I3 (4,50)01010405060

Sample cells: dp[2][4] = max(skip I2 → 10, take I2 → 40 + dp[1][1] = 40+10 = 50) = 50. dp[3][5] = max(skip I3 → 50, take I3 → 50 + dp[2][1] = 50+10 = 60) = 60. Answer: 60 (take I1 and I3). Complexity: \( O(nW) \).

কিছু cell-এর হিসাব: dp[2][4] = max(I2 বাদ → 10, I2 নিন → 40 + dp[1][1] = 40+10 = 50) = 50। dp[3][5] = max(I3 বাদ → 50, I3 নিন → 50 + dp[2][1] = 50+10 = 60) = 60। উত্তর: 60 (I1 আর I3 নিন)। Complexity: \( O(nW) \)।

Longest Common Subsequence (LCS)Longest Common Subsequence (LCS)

A subsequence keeps order but can skip characters. LCS of two strings = the longest subsequence present in both. DP: dp[i][j] = LCS length of first \( i \) chars of X and first \( j \) chars of Y.

Subsequence order ঠিক রাখে কিন্তু character skip করতে পারে। দুটো string-এর LCS = দুটোতেই থাকা সবচেয়ে লম্বা subsequence। DP: dp[i][j] = X-এর প্রথম \( i \) আর Y-এর প্রথম \( j \) character-এর LCS length।

\[ dp[i][j] = \begin{cases} dp[i-1][j-1] + 1 & \text{if } X_i = Y_j \\ \max(dp[i-1][j],\, dp[i][j-1]) & \text{otherwise} \end{cases} \]
Example (table): X = "ABCB", Y = "BDCB".
Example (table): X = "ABCB", Y = "BDCB"।
""BDCB
""00000
A00000
B01111
C01122
B01123

Answer: LCS length = 3 ("BCB"). Match cells (like B-B, C-C) take diagonal + 1; others take max of top and left. Complexity: \( O(mn) \).

উত্তর: LCS length = 3 ("BCB")। Match হওয়া cell (যেমন B-B, C-C) নেয় diagonal + 1; বাকিরা নেয় উপরের আর বামের max। Complexity: \( O(mn) \)।

Coin change with DP (fixes greedy!)DP দিয়ে coin change (greedy-র ভুল ঠিক করে!)

dp[v] = minimum coins to make amount \( v \). Base: dp[0] = 0. For each amount, try every coin:

dp[v] = amount \( v \) বানাতে minimum coin। Base: dp[0] = 0। প্রতিটা amount-এর জন্য প্রতিটা coin try করুন:

\[ dp[v] = 1 + \min_{c \,\in\, coins,\; c \le v} dp[v - c] \]
Example: coins {1, 3, 4}, amount 6.
  • dp[0]=0, dp[1]=1 (1), dp[2]=2 (1+1), dp[3]=1 (3), dp[4]=1 (4)
  • dp[5] = 1 + min(dp[4], dp[2], dp[1]) = 1 + 1 = 2 (4+1)
  • dp[6] = 1 + min(dp[5], dp[3], dp[2]) = 1 + dp[3] = 2 (3+3)
DP finds 2 coins — the answer greedy missed. Complexity: \( O(V \times \#coins) \).
Example: coin {1, 3, 4}, amount 6।
  • dp[0]=0, dp[1]=1 (1), dp[2]=2 (1+1), dp[3]=1 (3), dp[4]=1 (4)
  • dp[5] = 1 + min(dp[4], dp[2], dp[1]) = 1 + 1 = 2 (4+1)
  • dp[6] = 1 + min(dp[5], dp[3], dp[2]) = 1 + dp[3] = 2 (3+3)
DP পেল 2 coin — যেটা greedy miss করেছিল। Complexity: \( O(V \times \#coins) \)।

Greedy vs DPGreedy vs DP

GreedyDP
ChoicesChoiceOne "best now" choice, never revisitedএকটাই "এখন best" choice, আর ফেরা হয় নাTries all choices, keeps the bestসব choice try করে, best-টা রাখে
CorrectnessCorrectnessOnly for special problems (needs proof)শুধু বিশেষ problem-এ (proof লাগে)Always correct if subproblems defined rightSubproblem ঠিকমতো define করলে সবসময় সঠিক
SpeedSpeedUsually fasterসাধারণত বেশি fastSlower, uses table memoryএকটু slow, table-এর memory লাগে
ExamplesExampleActivity selection, Huffman, Dijkstra, Prim, KruskalKnapsack 0/1, LCS, coin change (general), Bellman-Ford
Note (exam tip): Fractional knapsack (items can be cut) → greedy by value/weight ratio works. 0/1 knapsack (no cutting) → greedy fails, DP needed. BUET often asks exactly this difference.
Note (exam tip): Fractional knapsack (item কাটা যায়) → value/weight ratio দিয়ে greedy কাজ করে। 0/1 knapsack (কাটা যায় না) → greedy fail, DP লাগে। BUET প্রায়ই ঠিক এই পার্থক্যটা জিজ্ঞেস করে।

7. Graph Algorithms7. Graph Algorithms

BFS and DFSBFS আর DFS

BFS (Breadth-First Search) visits the graph level by level, using a queue. It first sees all neighbours, then neighbours of neighbours. DFS (Depth-First Search) goes as deep as possible along one path, then backtracks, using a stack (or recursion).

BFS (Breadth-First Search) graph-টা level by level ঘোরে, একটা queue ব্যবহার করে। আগে সব neighbour দেখে, তারপর neighbour-দের neighbour। DFS (Depth-First Search) এক path ধরে যত গভীরে যাওয়া যায় যায়, তারপর backtrack করে, একটা stack (বা recursion) ব্যবহার করে।

// BFS from source s (adjacency list, n nodes)
void bfs(int s) {
    queue<int> q;
    visited[s] = 1; q.push(s);
    while (!q.empty()) {
        int u = q.front(); q.pop();
        printf("%d ", u);                 // visit u
        for (int v : adj[u])              // all neighbours
            if (!visited[v]) { visited[v] = 1; q.push(v); }
    }
}

// DFS (recursive)
void dfs(int u) {
    visited[u] = 1;
    printf("%d ", u);                     // visit u
    for (int v : adj[u])
        if (!visited[v]) dfs(v);
}
1 2 3 4 5
Sample graph. Edges: 1–2, 1–3, 2–4, 3–4, 2–5, 4–5. We traverse from node 1 (neighbours in number order).Sample graph। Edge: 1–2, 1–3, 2–4, 3–4, 2–5, 4–5। Node 1 থেকে traverse করব (neighbour ছোট number আগে)।
Example (BFS trace from 1):
  • Visit 1. Queue: [2, 3]
  • Pop 2, visit. Its new neighbours: 4, 5. Queue: [3, 4, 5]
  • Pop 3, visit. 4 already queued. Queue: [4, 5]
  • Pop 4, visit. Pop 5, visit.
BFS order: 1, 2, 3, 4, 5. (Level 0: 1; level 1: 2, 3; level 2: 4, 5.)
DFS trace from 1: visit 1 → go 2 → go 4 (2's smallest unvisited) → go 3 (4's neighbour) → backtrack to 4 → go 5. DFS order: 1, 2, 4, 3, 5.
Example (1 থেকে BFS trace):
  • 1 visit। Queue: [2, 3]
  • 2 pop, visit। এর নতুন neighbour: 4, 5। Queue: [3, 4, 5]
  • 3 pop, visit। 4 আগেই queue-তে। Queue: [4, 5]
  • 4 pop, visit। 5 pop, visit।
BFS order: 1, 2, 3, 4, 5। (Level 0: 1; level 1: 2, 3; level 2: 4, 5।)
1 থেকে DFS trace: 1 visit → 2-তে যান → 4-এ যান (2-এর সবচেয়ে ছোট unvisited) → 3-এ যান (4-এর neighbour) → 4-এ backtrack → 5-এ যান। DFS order: 1, 2, 4, 3, 5
BFSDFS
Data structureData structureQueueStack / recursion
ComplexityComplexity\( O(V + E) \)\( O(V + E) \)
ApplicationsApplicationShortest path in unweighted graph, level order, bipartite checkUnweighted graph-এ shortest path, level order, bipartite checkCycle detection, topological sort, connected components, maze solvingCycle detection, topological sort, connected component, maze solve

Topological sort (idea)Topological sort (idea)

For a directed acyclic graph (DAG): order the nodes so every edge goes from earlier to later. Think of courses with prerequisites — take a course only after its prerequisites. Two ways: (1) DFS: after finishing a node, push it on a stack; pop all at the end. (2) Kahn's algorithm: repeatedly remove a node with in-degree 0 (no incoming edges). A cycle makes topological sort impossible. Example: edges 5→2, 5→0, 4→0, 4→1, 2→3, 3→1 → one valid order: 5, 4, 2, 3, 1, 0.

Directed acyclic graph (DAG)-এর জন্য: node-গুলো এমনভাবে সাজান যেন প্রতিটা edge আগের node থেকে পরের node-এ যায়। Prerequisite-ওয়ালা course-এর মতো ভাবুন — আগে prerequisite, তারপর course। দুই উপায়: (1) DFS: একটা node শেষ হলে stack-এ push করুন; শেষে সব pop করুন। (2) Kahn's algorithm: বারবার in-degree 0 (কোনো incoming edge নেই) এমন node সরান। Cycle থাকলে topological sort সম্ভব না। Example: edge 5→2, 5→0, 4→0, 4→1, 2→3, 3→1 → একটা valid order: 5, 4, 2, 3, 1, 0।

Dijkstra's algorithm (single-source shortest path)Dijkstra's algorithm (single-source shortest path)

Finds shortest distances from one source to all nodes, when edge weights are non-negative. Greedy idea: keep a distance array. Repeatedly pick the unvisited node with the smallest distance, mark it final, and relax its edges (relax means: if dist[u] + w < dist[v], update dist[v]).

Edge weight non-negative হলে এক source থেকে সব node-এর shortest distance বের করে। Greedy idea: একটা distance array রাখুন। বারবার সবচেয়ে ছোট distance-ওয়ালা unvisited node নিন, তাকে final করুন, আর তার edge-গুলো relax করুন (relax মানে: dist[u] + w < dist[v] হলে dist[v] update)।

4 1 2 5 8 A B C D
Weighted graph for Dijkstra. Edges: A–B (4), A–C (1), C–B (2), B–D (5), C–D (8). Source = A.Dijkstra-র জন্য weighted graph। Edge: A–B (4), A–C (1), C–B (2), B–D (5), C–D (8)। Source = A।
Example (step-by-step table): Start: dist[A]=0, others ∞.
Example (step-by-step table): শুরুতে: dist[A]=0, বাকিরা ∞।
StepStepPicked nodeনেওয়া nodedist[A]dist[B]dist[C]dist[D]What happenedকী হলো
00initialশুরু
1A (0)041relax A–B: 0+4; A–C: 0+1relax A–B: 0+4; A–C: 0+1
2C (1)0319relax C–B: 1+2=3 < 4 → update; C–D: 1+8=9relax C–B: 1+2=3 < 4 → update; C–D: 1+8=9
3B (3)0318relax B–D: 3+5=8 < 9 → updaterelax B–D: 3+5=8 < 9 → update
4D (8)0318doneশেষ

Final shortest distances from A: B = 3 (path A→C→B), C = 1, D = 8 (path A→C→B→D). Note how the direct edge A–B (4) lost to the path through C (3).

A থেকে final shortest distance: B = 3 (path A→C→B), C = 1, D = 8 (path A→C→B→D)। লক্ষ করুন সরাসরি A–B edge (4) হেরে গেল C-এর ভেতর দিয়ে path-এর (3) কাছে।

Dijkstra algorithm animation
Dijkstra's algorithm animated: it grows a "settled" set outward from the source, always settling the closest node next.Dijkstra's algorithm-এর animation: source থেকে "settled" set বাইরের দিকে বাড়ে, প্রতিবার সবচেয়ে কাছের node-টা settle হয়।
Note (exam favourite): Dijkstra fails with negative edge weights. It marks a node final too early, and a later negative edge could have given a shorter path — but Dijkstra never rechecks. Complexity: \( O((V+E)\log V) \) with a min-heap, \( O(V^2) \) with a simple array.
Note (exam favourite): Dijkstra negative edge weight-এ fail করে। এটা কোনো node-কে খুব আগে final করে দেয়, অথচ পরের কোনো negative edge আরো ছোট path দিতে পারত — কিন্তু Dijkstra আর recheck করে না। Complexity: min-heap দিয়ে \( O((V+E)\log V) \), সাধারণ array দিয়ে \( O(V^2) \)।

Bellman-Ford (handles negative weights)Bellman-Ford (negative weight সামলায়)

Idea: relax every edge, and repeat this \( V-1 \) times. Any shortest path has at most \( V-1 \) edges, so after \( V-1 \) rounds all distances are correct — even with negative edges. Bonus: run one extra round; if any distance still improves, the graph has a negative cycle (then "shortest path" has no meaning). Complexity: \( O(VE) \) — slower than Dijkstra, but safer.

Idea: প্রতিটা edge relax করুন, আর এটা \( V-1 \) বার repeat করুন। যেকোনো shortest path-এ সর্বোচ্চ \( V-1 \)-টা edge থাকে, তাই \( V-1 \) round পরে সব distance সঠিক — negative edge থাকলেও। Bonus: আরো এক round চালান; কোনো distance এখনো কমলে graph-এ negative cycle আছে (তখন "shortest path"-এর কোনো মানে নেই)। Complexity: \( O(VE) \) — Dijkstra-র চেয়ে slow, কিন্তু safe।

Example (negative-cycle detection — asked April 2019): Graph with 3 nodes A, B, C. Edges: A→B (1), B→C (−3), C→A (1). Cycle total = 1 + (−3) + 1 = −1, so this is a negative cycle.
  • Run Bellman-Ford from A. \( V-1 = 2 \) normal rounds finish. Say dist[A]=0, dist[B]=1, dist[C]=−2 after them.
  • Now run one extra (the \( V \)-th) relaxation pass. Edge C→A: dist[C] + 1 = −2 + 1 = −1 < dist[A] = 0. A distance still improved!
  • Rule: after \( V-1 \) rounds, all correct shortest distances are final. So if any edge still relaxes in the extra pass → a negative cycle is reachable → report "no valid shortest path". If nothing improves → no negative cycle, distances are safe.
Example (negative-cycle detection — April 2019-এ এসেছিল): 3 node-এর graph A, B, C। Edge: A→B (1), B→C (−3), C→A (1)। Cycle-এর মোট = 1 + (−3) + 1 = −1, তাই এটা negative cycle।
  • A থেকে Bellman-Ford চালান। \( V-1 = 2 \)-টা normal round শেষ হলো। ধরুন তখন dist[A]=0, dist[B]=1, dist[C]=−2।
  • এবার আরো একটা extra (\( V \)-তম) relaxation pass চালান। Edge C→A: dist[C] + 1 = −2 + 1 = −1 < dist[A] = 0। একটা distance এখনো কমলো!
  • নিয়ম: \( V-1 \) round-এর পর সব সঠিক shortest distance final হয়ে যায়। তাই extra pass-এ কোনো edge relax হলে → reachable negative cycle আছে → বলুন "valid shortest path নেই"। কিছু না কমলে → negative cycle নেই, distance-গুলো safe।

Minimum Spanning Tree (MST): Prim and KruskalMinimum Spanning Tree (MST): Prim আর Kruskal

A spanning tree connects all \( V \) nodes using exactly \( V-1 \) edges, with no cycle. The minimum spanning tree has the smallest total edge weight. Both famous algorithms are greedy:

  • Prim: grow one tree. Start from any node; repeatedly add the cheapest edge that connects the tree to a new node.
  • Kruskal: sort all edges by weight. Take edges smallest first, but skip any edge that makes a cycle. Stop after \( V-1 \) edges.

Spanning tree সব \( V \)-টা node-কে ঠিক \( V-1 \)-টা edge দিয়ে connect করে, কোনো cycle ছাড়া। Minimum spanning tree-র মোট edge weight সবচেয়ে কম। দুটো বিখ্যাত algorithm-ই greedy:

  • Prim: একটা tree বড় করুন। যেকোনো node থেকে শুরু; বারবার সবচেয়ে সস্তা edge-টা নিন যা tree-কে একটা নতুন node-এর সাথে connect করে।
  • Kruskal: সব edge weight দিয়ে sort করুন। ছোট থেকে edge নিন, কিন্তু cycle বানায় এমন edge বাদ দিন। \( V-1 \)-টা edge হলে থামুন।
Example (both algorithms, same graph): Use the Dijkstra graph above: A–B (4), A–C (1), C–B (2), B–D (5), C–D (8).
Kruskal: sorted edges: A–C(1), C–B(2), A–B(4), B–D(5), C–D(8).
  • Take A–C (1). Take C–B (2).
  • A–B (4)? A and B already connected (A–C–B) → cycle → skip.
  • Take B–D (5). Now 3 edges = V−1 → stop.
MST = {A–C, C–B, B–D}, total weight = 1 + 2 + 5 = 8.
Prim (start A): cheapest edge from {A} is A–C (1) → add C. Cheapest from {A,C}: C–B (2) → add B. Cheapest from {A,C,B}: B–D (5) → add D. Same MST, weight 8. (Prim and Kruskal always give the same total weight.)
Example (একই graph-এ দুই algorithm): উপরের Dijkstra graph-টাই নিন: A–B (4), A–C (1), C–B (2), B–D (5), C–D (8)।
Kruskal: sorted edge: A–C(1), C–B(2), A–B(4), B–D(5), C–D(8)।
  • A–C (1) নিন। C–B (2) নিন।
  • A–B (4)? A আর B আগেই connected (A–C–B) → cycle → বাদ
  • B–D (5) নিন। এখন 3টা edge = V−1 → থামুন।
MST = {A–C, C–B, B–D}, মোট weight = 1 + 2 + 5 = 8
Prim (A থেকে শুরু): {A} থেকে সবচেয়ে সস্তা edge A–C (1) → C যোগ। {A,C} থেকে সস্তা: C–B (2) → B যোগ। {A,C,B} থেকে সস্তা: B–D (5) → D যোগ। একই MST, weight 8। (Prim আর Kruskal সবসময় একই মোট weight দেয়।)
Minimum spanning tree example
A minimum spanning tree (bold edges) of a weighted graph: all nodes connected, total weight minimum, no cycles.একটা weighted graph-এর minimum spanning tree (মোটা edge): সব node connected, মোট weight minimum, কোনো cycle নেই।

DSU (Disjoint Set Union) — how Kruskal detects cyclesDSU (Disjoint Set Union) — Kruskal যেভাবে cycle ধরে

DSU (also called Union-Find) keeps track of groups. Two operations: find(x) — which group is x in (follow parents to the root)? union(x, y) — merge two groups. In Kruskal, before taking edge (u, v): if find(u) == find(v) they are already connected, so the edge would make a cycle → skip. Otherwise take the edge and union them. With "path compression" and "union by rank", each operation is almost \( O(1) \). Kruskal total: \( O(E \log E) \) (for sorting).

DSU (Union-Find-ও বলে) group-এর হিসাব রাখে। দুটো operation: find(x) — x কোন group-এ (parent ধরে root পর্যন্ত যান)? union(x, y) — দুটো group merge করুন। Kruskal-এ edge (u, v) নেওয়ার আগে: find(u) == find(v) হলে তারা আগেই connected, মানে edge-টা cycle বানাবে → বাদ। নাহলে edge নিন আর union করুন। "Path compression" আর "union by rank" দিলে প্রতিটা operation প্রায় \( O(1) \)। Kruskal মোট: \( O(E \log E) \) (sorting-এর জন্য)।

int parent[N];
int find(int x) {
    if (parent[x] == x) return x;
    return parent[x] = find(parent[x]);   // path compression
}
void unite(int x, int y) { parent[find(x)] = find(y); }
Note (quick recall): Shortest path ≠ MST! Dijkstra minimizes path length from one source; MST minimizes total weight of the whole tree. Also memorize: BFS/DFS \( O(V+E) \), Dijkstra \( O((V+E)\log V) \), Bellman-Ford \( O(VE) \), Prim \( O(E \log V) \), Kruskal \( O(E \log E) \).
Note (quick recall): Shortest path ≠ MST! Dijkstra এক source থেকে path length ছোট করে; MST পুরো tree-র মোট weight ছোট করে। আরো মুখস্থ রাখুন: BFS/DFS \( O(V+E) \), Dijkstra \( O((V+E)\log V) \), Bellman-Ford \( O(VE) \), Prim \( O(E \log V) \), Kruskal \( O(E \log E) \)।

Critical Path in a Task Graph (Real Exam!)Task Graph-এ Critical Path (Real Exam!)

Imagine a project made of tasks. Each task has a duration, and some tasks must wait for others (dependencies). Tasks with no dependency between them can run in parallel. The whole setup is a DAG (directed acyclic graph): task = node, dependency = edge.

The critical path is the longest path through this DAG (sum of task durations along the path). Key fact: the minimum possible completion time of the whole project = length of the critical path. Why? Every task on that path must run one after another — no parallelism can shorten it. Every other task can fit alongside.

How to find it — forward pass in topological order:

  • Earliest Start (ES) of a task = max of Earliest Finish of all its dependencies (0 if none).
  • Earliest Finish (EF) = ES + duration.
  • Minimum completion time = max EF over all tasks. The critical path is the chain of tasks that produces this max.

ধরুন একটা project অনেকগুলো task দিয়ে তৈরি। প্রতিটা task-এর একটা duration আছে, আর কিছু task-কে অন্য task-এর জন্য অপেক্ষা করতে হয় (dependency)। যাদের মধ্যে dependency নেই, তারা parallel-এ চলতে পারে। পুরো ব্যাপারটা একটা DAG (directed acyclic graph): task = node, dependency = edge।

Critical path হলো এই DAG-এর ভিতর দিয়ে longest path (path-এর task duration-গুলোর যোগফল)। মূল কথা: পুরো project-এর minimum possible completion time = critical path-এর length। কেন? ওই path-এর প্রতিটা task একটার পর একটা চলতেই হবে — কোনো parallelism এটা ছোট করতে পারে না। বাকি সব task পাশে পাশে চলে যায়।

কীভাবে বের করবেন — topological order-এ forward pass:

  • একটা task-এর Earliest Start (ES) = তার সব dependency-র Earliest Finish-এর max (dependency না থাকলে 0)।
  • Earliest Finish (EF) = ES + duration।
  • Minimum completion time = সব task-এর মধ্যে max EF। যে task-এর chain এই max বানায়, সেটাই critical path।
Example (full forward pass): Project with 6 tasks:
TaskDurationDepends on
A3
B2
C4A
D2A, B
E3D
F2C, E
Forward pass (ES = max EF of dependencies, EF = ES + duration):
  • A: ES = 0, EF = 0 + 3 = 3
  • B: ES = 0, EF = 0 + 2 = 2
  • C: ES = EF(A) = 3, EF = 3 + 4 = 7
  • D: ES = max(EF(A), EF(B)) = max(3, 2) = 3, EF = 3 + 2 = 5
  • E: ES = EF(D) = 5, EF = 5 + 3 = 8
  • F: ES = max(EF(C), EF(E)) = max(7, 8) = 8, EF = 8 + 2 = 10
Minimum completion time = 10. Trace back the max at each step: F came from E (8), E from D (5), D from A (3). So the critical path is A → D → E → F, length 3 + 2 + 3 + 2 = 10. Path A → C → F is only 3 + 4 + 2 = 9, so C has 1 unit of slack.
Example (পুরো forward pass): 6টা task-এর project:
TaskDurationDepends on
A3
B2
C4A
D2A, B
E3D
F2C, E
Forward pass (ES = dependency-গুলোর max EF, EF = ES + duration):
  • A: ES = 0, EF = 0 + 3 = 3
  • B: ES = 0, EF = 0 + 2 = 2
  • C: ES = EF(A) = 3, EF = 3 + 4 = 7
  • D: ES = max(EF(A), EF(B)) = max(3, 2) = 3, EF = 3 + 2 = 5
  • E: ES = EF(D) = 5, EF = 5 + 3 = 8
  • F: ES = max(EF(C), EF(E)) = max(7, 8) = 8, EF = 8 + 2 = 10
Minimum completion time = 10। প্রতিটা step-এ max কোথা থেকে এলো তা পিছনে ট্রেস করুন: F এসেছে E (8) থেকে, E এসেছে D (5) থেকে, D এসেছে A (3) থেকে। তাই critical path হলো A → D → E → F, length 3 + 2 + 3 + 2 = 10। Path A → C → F মাত্র 3 + 4 + 2 = 9, তাই C-র 1 unit slack আছে।
A 3 B 2 C 4 D 2 E 3 F 2 Critical path: A → D → E → F = 10
The task DAG. Numbers inside nodes are durations. The red chain A → D → E → F is the longest (critical) path — its length 10 is the minimum completion time.Task DAG। Node-এর ভিতরের সংখ্যা হলো duration। লাল chain A → D → E → F হলো longest (critical) path — এর length 10-ই minimum completion time।
Note (real exam): This was asked in October 2018: given tasks with durations and dependencies, find the minimum completion time. Answer = critical path length. In a DAG the longest path is found in \( O(V+E) \) with a topological order + forward pass. Show the ES/EF table — that is where the marks are.
Note (real exam): এটা October 2018-তে এসেছিল: duration আর dependency-সহ task দিয়ে minimum completion time বের করতে হবে। Answer = critical path-এর length। DAG-এ longest path বের হয় \( O(V+E) \)-তে — topological order + forward pass দিয়ে। ES/EF table-টা দেখান — মার্কস ওখানেই।

8. P, NP and Hard Problems (Real Exam Topic!)8. P, NP আর Hard Problems (Real Exam Topic!)

Some problems are easy for computers. Some are (probably) very hard. Complexity theory puts problems into classes. BUET has directly asked to define these classes and to draw their relationship diagram. Learn the four names below very well.

কিছু problem computer-এর জন্য সহজ। কিছু (সম্ভবত) খুবই কঠিন। Complexity theory problem-গুলোকে class-এ ভাগ করে। BUET সরাসরি এই class-গুলোর definition আর relationship diagram আঁকতে বলেছে। নিচের চারটা নাম খুব ভালো করে শিখুন।

Class P (Polynomial time)Class P (Polynomial time)

P = the set of problems a computer can solve in polynomial time — meaning time like \( O(n) \), \( O(n^2) \), \( O(n^3) \). These are the "easy" (tractable) problems.

  • Examples: sorting an array (\( O(n \log n) \)), searching, shortest path with Dijkstra (\( O((V+E)\log V) \)), matrix multiplication.

P = সেই problem-গুলোর set যেগুলো computer polynomial time-এ solve করতে পারে — মানে \( O(n) \), \( O(n^2) \), \( O(n^3) \)-এর মতো time। এগুলো "সহজ" (tractable) problem।

  • Example: array sort করা (\( O(n \log n) \)), searching, Dijkstra দিয়ে shortest path (\( O((V+E)\log V) \)), matrix multiplication।

Class NP (Nondeterministic Polynomial time)Class NP (Nondeterministic Polynomial time)

NP = the set of problems where, if someone gives you a solution, you can verify (check) it in polynomial time. Finding the solution may be hard, but checking is easy.

  • Examples: SAT (given a true/false setting of variables, checking the formula is easy), TSP decision version ("is there a tour of length ≤ k?" — given a tour, adding up its length is easy), Sudoku (checking a filled board is easy).
  • Every problem in P is also in NP — if you can solve it fast, you can surely check a given answer fast. So \( P \subseteq NP \).

NP = সেই problem-গুলোর set যেখানে কেউ আপনাকে একটা solution দিয়ে দিলে, আপনি সেটা polynomial time-এ verify (check) করতে পারেন। Solution খুঁজে বের করা কঠিন হতে পারে, কিন্তু check করা সহজ।

  • Example: SAT (variable-গুলোর true/false মান দেওয়া থাকলে formula check করা সহজ), TSP decision version ("length ≤ k-এর কোনো tour আছে কি?" — tour দেওয়া থাকলে length যোগ করা সহজ), Sudoku (ভরা board check করা সহজ)।
  • P-এর প্রতিটা problem NP-তেও আছে — fast solve করতে পারলে দেওয়া answer fast check-ও করতে পারবেন। তাই \( P \subseteq NP \)।

NP-Complete and NP-HardNP-Complete আর NP-Hard

  • NP-Complete = the hardest problems inside NP. A problem is NP-Complete if (1) it is in NP, and (2) every problem in NP can be converted (reduced) to it in polynomial time. Solve one NP-Complete problem fast → you solve ALL of NP fast. Examples: SAT (the first one, by Cook's theorem), 3-SAT, TSP decision version, vertex cover, graph coloring, subset sum.
  • NP-Hard = at least as hard as every problem in NP, but it does not have to be in NP itself (its answer may not even be checkable fast, or it may not be a yes/no problem). Examples: TSP optimization version ("find the shortest tour" — even checking that a tour is the shortest is hard), the halting problem (not solvable at all!).
  • Relationship: NP-Complete = NP ∩ NP-Hard. NP-Complete problems are exactly the NP-Hard problems that also sit inside NP.
  • NP-Complete = NP-এর ভেতরের সবচেয়ে কঠিন problem। একটা problem NP-Complete হয় যদি (1) এটা NP-তে থাকে, আর (2) NP-এর প্রতিটা problem-কে polynomial time-এ এটাতে convert (reduce) করা যায়। একটা NP-Complete problem fast solve করলে → পুরো NP fast solve হয়ে যায়। Example: SAT (প্রথমটা, Cook's theorem দিয়ে), 3-SAT, TSP decision version, vertex cover, graph coloring, subset sum।
  • NP-Hard = NP-এর প্রতিটা problem-এর মতো বা তার চেয়েও কঠিন, কিন্তু নিজে NP-তে থাকা জরুরি না (এর answer fast check-ও করা নাও যেতে পারে, বা এটা yes/no problem নাও হতে পারে)। Example: TSP optimization version ("সবচেয়ে ছোট tour বের করুন" — একটা tour-ই যে সবচেয়ে ছোট, সেটা check করাও কঠিন), halting problem (আদৌ solve-ই করা যায় না!)।
  • Relationship: NP-Complete = NP ∩ NP-Hard। NP-Hard problem-গুলোর মধ্যে যেগুলো NP-এর ভেতরেও আছে, সেগুলোই NP-Complete।

The relationship diagram (BUET asked to draw this!)Relationship diagram (BUET এটা আঁকতে বলেছে!)

All computational problems NP P sorting, shortest path NP-Hard TSP optimization, halting problem NP- Complete SAT, TSP (decision) NP-Complete = NP ∩ NP-Hard Assuming P ≠ NP (the version everyone draws in exams)

Read the diagram like this: P sits inside NP. NP-Hard sticks out to the right — part of it is inside NP, part is outside. The overlap of NP and NP-Hard is exactly NP-Complete. If P = NP were true, the picture would collapse: P, NP and NP-Complete would all become one same set (and NP-Hard would contain that whole set).

Diagram-টা এভাবে পড়ুন: P থাকে NP-এর ভেতরে। NP-Hard ডান দিকে বেরিয়ে আছে — এর কিছু অংশ NP-এর ভেতরে, কিছু বাইরে। NP আর NP-Hard-এর overlap-টাই হলো NP-Complete। যদি P = NP সত্যি হতো, ছবিটা ভেঙে পড়ত: P, NP আর NP-Complete সব একই set হয়ে যেত (আর NP-Hard পুরো set-টাকে ধারণ করত)।

P vs NP — the biggest open questionP vs NP — সবচেয়ে বড় open question

Is P = NP? In words: if an answer is easy to check, is it also easy to find? Nobody knows. It is the most famous open problem in computer science, with a $1 million Clay Millennium Prize. Most researchers believe P ≠ NP — that is why we draw the diagram above with P as a strict smaller circle.

P = NP কি? সহজ কথায়: একটা answer যদি সহজে check করা যায়, তাহলে কি সেটা সহজে খুঁজেও বের করা যায়? কেউ জানে না। এটা computer science-এর সবচেয়ে বিখ্যাত open problem, $1 million Clay Millennium Prize আছে এর জন্য। বেশিরভাগ researcher বিশ্বাস করেন P ≠ NP — তাই উপরের diagram-এ P-কে ছোট আলাদা circle হিসেবে আঁকা হয়।

Approximation algorithm vs Heuristic algorithmApproximation algorithm vs Heuristic algorithm

NP-Hard problems have no known fast exact algorithm. So in practice we use two kinds of "good enough" methods. BUET asked the difference directly.

NP-Hard problem-এর জন্য কোনো fast exact algorithm জানা নেই। তাই বাস্তবে আমরা দুই ধরনের "good enough" পদ্ধতি ব্যবহার করি। BUET সরাসরি এদের পার্থক্য জিজ্ঞেস করেছে।

Approximation algorithmApproximation algorithm Heuristic algorithmHeuristic algorithm
GuaranteeGuarantee Has a provable (mathematical) bound on how far the answer can be from the optimal.Answer optimal থেকে সর্বোচ্চ কতদূর হতে পারে তার provable (mathematical) bound আছে। No guarantee at all. It just works well in practice, usually.কোনো guarantee নেই। সাধারণত practice-এ ভালো কাজ করে, এটুকুই।
Answer qualityAnswer quality Example: a 2-approximation for vertex cover always returns a cover at most 2× the optimal size — proven.Example: vertex cover-এর 2-approximation সবসময় optimal-এর সর্বোচ্চ 2× size-এর cover দেয় — proven। Can be very good or very bad; on some inputs it may fail badly and we cannot predict when.খুব ভালো বা খুব খারাপ হতে পারে; কোনো কোনো input-এ ভীষণ খারাপ করতে পারে, কখন করবে বলা যায় না।
ExamplesExample 2-approximation for vertex cover, Christofides' 1.5-approximation for metric TSP.Vertex cover-এর 2-approximation, metric TSP-র Christofides' 1.5-approximation। A* search heuristics, hill climbing, genetic algorithms, greedy rules of thumb.A* search-এর heuristics, hill climbing, genetic algorithm, greedy rule of thumb।
One-line memory trickএক লাইনে মনে রাখার trick "Approximation = promise on paper.""Approximation = কাগজে promise।" "Heuristic = hope from experience.""Heuristic = অভিজ্ঞতা থেকে আশা।"
Note (real exam): This exact topic was asked in April 2024 ("Define P and NP. Difference between approximation and heuristic algorithm?") and in April 2019 ("Draw the diagram showing the relationship between P, NP, NP-Hard, NP-Complete."). Practice writing the four definitions in 1–2 lines each, and practice drawing the diagram above from memory in under a minute.
Note (real exam): ঠিক এই topic-টা April 2024-এ এসেছিল ("P আর NP define করুন। Approximation আর heuristic algorithm-এর পার্থক্য কী?") আর April 2019-এও ("P, NP, NP-Hard, NP-Complete-এর relationship দেখিয়ে diagram আঁকুন।")। চারটা definition ১–২ লাইনে লেখা practice করুন, আর উপরের diagram-টা এক মিনিটের মধ্যে মুখস্থ আঁকা practice করুন।

Practice Questions (Admission Style)Practice Questions (Admission Style)

Q1. What is the time complexity of binary search on a sorted array of n elements?
  • (a) \( O(n) \)
  • (b) \( O(\log n) \)
  • (c) \( O(n \log n) \)
  • (d) \( O(1) \)
Q1. n element-এর একটা sorted array-তে binary search-এর time complexity কত?
  • (a) \( O(n) \)
  • (b) \( O(\log n) \)
  • (c) \( O(n \log n) \)
  • (d) \( O(1) \)
Show Answerউত্তর দেখুন
Answer: (b) — each comparison throws away half the remaining elements: \( n \to n/2 \to n/4 \to \dots \to 1 \). If it takes k steps, \( 2^k = n \), so \( k = \log_2 n \).
Answer: (b) — প্রতিটা comparison বাকি element-এর অর্ধেক বাদ দেয়: \( n \to n/2 \to n/4 \to \dots \to 1 \)। k step লাগলে \( 2^k = n \), তাই \( k = \log_2 n \)।
Q2. Which of these sorting algorithms is NOT stable (in its usual form)?
  • (a) Merge sort
  • (b) Insertion sort
  • (c) Quick sort
  • (d) Bubble sort
Q2. নিচের কোন sorting algorithm (সাধারণ form-এ) stable না?
  • (a) Merge sort
  • (b) Insertion sort
  • (c) Quick sort
  • (d) Bubble sort
Show Answerউত্তর দেখুন
Answer: (c) — quick sort's partition swaps elements over long distances, so equal elements can change their original order. Merge, insertion, and bubble sort compare neighbours (or merge carefully), so equal elements keep their order.
Answer: (c) — quick sort-এর partition অনেক দূরের element swap করে, তাই সমান element-দের আগের order বদলে যেতে পারে। Merge, insertion আর bubble sort পাশাপাশি compare করে (বা সাবধানে merge করে), তাই সমান element-রা order ঠিক রাখে।
Q3. What is the complexity of this code?
for (i = 0; i < n; i++)
    for (j = 0; j < i; j++)
        sum++;
  • (a) \( O(n) \)
  • (b) \( O(n \log n) \)
  • (c) \( O(n^2) \)
  • (d) \( O(2^n) \)
Q3. এই code-এর complexity কত?
for (i = 0; i < n; i++)
    for (j = 0; j < i; j++)
        sum++;
  • (a) \( O(n) \)
  • (b) \( O(n \log n) \)
  • (c) \( O(n^2) \)
  • (d) \( O(2^n) \)
Show Answerউত্তর দেখুন
Answer: (c) — the inner loop runs 0 + 1 + 2 + ... + (n−1) = n(n−1)/2 times in total. Dropping constants, this is \( O(n^2) \).
Answer: (c) — inner loop মোট চলে 0 + 1 + 2 + ... + (n−1) = n(n−1)/2 বার। Constant বাদ দিলে এটা \( O(n^2) \)।
Q4. The worst case of quick sort happens when:
  • (a) The array is randomly shuffled
  • (b) The pivot always lands in the middle
  • (c) The array is already sorted (with first/last pivot)
  • (d) All elements are negative
Q4. Quick sort-এর worst case কখন হয়?
  • (a) Array randomly এলোমেলো থাকলে
  • (b) Pivot সবসময় মাঝে পড়লে
  • (c) Array আগে থেকেই sorted থাকলে (first/last pivot-এ)
  • (d) সব element negative হলে
Show Answerউত্তর দেখুন
Answer: (c) — with a first or last element pivot on a sorted array, partition always splits into sizes n−1 and 0. The recurrence becomes T(n) = T(n−1) + n = \( O(n^2) \). A middle-landing pivot is actually the best case.
Answer: (c) — sorted array-তে first বা last element pivot নিলে partition সবসময় n−1 আর 0 size-এ ভাগ করে। Recurrence হয় T(n) = T(n−1) + n = \( O(n^2) \)। Pivot মাঝে পড়া বরং best case।
Q5. BFS uses which data structure, and DFS uses which?
  • (a) BFS: stack, DFS: queue
  • (b) BFS: queue, DFS: stack
  • (c) Both use queue
  • (d) Both use heap
Q5. BFS কোন data structure ব্যবহার করে, আর DFS কোনটা?
  • (a) BFS: stack, DFS: queue
  • (b) BFS: queue, DFS: stack
  • (c) দুটোই queue
  • (d) দুটোই heap
Show Answerউত্তর দেখুন
Answer: (b) — BFS visits level by level, so it needs FIFO order → queue. DFS goes deep and backtracks, which is LIFO → stack (recursion uses the call stack).
Answer: (b) — BFS level by level ঘোরে, তাই FIFO order লাগে → queue। DFS গভীরে গিয়ে backtrack করে, যেটা LIFO → stack (recursion-এ call stack ব্যবহার হয়)।
Q6. Solve with the Master theorem: \( T(n) = 8T(n/2) + n^2 \).
  • (a) \( O(n^2) \)
  • (b) \( O(n^2 \log n) \)
  • (c) \( O(n^3) \)
  • (d) \( O(n \log n) \)
Q6. Master theorem দিয়ে solve করুন: \( T(n) = 8T(n/2) + n^2 \)।
  • (a) \( O(n^2) \)
  • (b) \( O(n^2 \log n) \)
  • (c) \( O(n^3) \)
  • (d) \( O(n \log n) \)
Show Answerউত্তর দেখুন
Answer: (c) — here a = 8, b = 2, d = 2. Compare: \( \log_2 8 = 3 \) and d = 2. Since \( d < \log_b a \), the recursion wins: \( T(n) = O(n^{\log_2 8}) = O(n^3) \).
Answer: (c) — এখানে a = 8, b = 2, d = 2। Compare: \( \log_2 8 = 3 \) আর d = 2। যেহেতু \( d < \log_b a \), recursion-ই বড়: \( T(n) = O(n^{\log_2 8}) = O(n^3) \)।
Q7. Dijkstra's algorithm may give WRONG answers when:
  • (a) The graph is directed
  • (b) The graph has negative edge weights
  • (c) The graph has more edges than nodes
  • (d) The source has many neighbours
Q7. Dijkstra's algorithm কখন ভুল উত্তর দিতে পারে?
  • (a) Graph directed হলে
  • (b) Graph-এ negative edge weight থাকলে
  • (c) Node-এর চেয়ে edge বেশি হলে
  • (d) Source-এর অনেক neighbour থাকলে
Show Answerউত্তর দেখুন
Answer: (b) — Dijkstra finalizes the closest node greedily and never revisits it. A negative edge found later could make a shorter path to an already-final node, but Dijkstra ignores it. Use Bellman-Ford (\( O(VE) \)) for negative weights.
Answer: (b) — Dijkstra greedy ভাবে সবচেয়ে কাছের node final করে দেয়, আর ফিরে দেখে না। পরে পাওয়া কোনো negative edge already-final node-এ ছোট path দিতে পারত, কিন্তু Dijkstra সেটা ধরে না। Negative weight-এ Bellman-Ford (\( O(VE) \)) ব্যবহার করুন।
Q8. A graph has 6 nodes. How many edges does any spanning tree of it have? And what does Kruskal do when the next smallest edge connects two nodes already in the same DSU set?
Q8. একটা graph-এ 6টা node আছে। এর যেকোনো spanning tree-তে কয়টা edge থাকবে? আর পরের সবচেয়ে ছোট edge-টা যদি একই DSU set-এর দুটো node-কে connect করে, Kruskal কী করে?
Show Answerউত্তর দেখুন
Answer: A spanning tree of V nodes always has exactly V − 1 edges, so 6 − 1 = 5 edges. If find(u) == find(v), the two endpoints are already connected, so adding the edge would create a cycle — Kruskal skips that edge and moves to the next one.
Answer: V node-এর spanning tree-তে সবসময় ঠিক V − 1টা edge থাকে, তাই 6 − 1 = 5টা edge। যদি find(u) == find(v) হয়, দুই প্রান্ত আগেই connected, মানে edge-টা নিলে cycle হবে — Kruskal edge-টা বাদ দিয়ে পরেরটায় যায়।
Q9. Trace insertion sort on the array [9, 4, 6, 2]. Show the array after processing each key.
Q9. [9, 4, 6, 2] array-তে insertion sort trace করুন। প্রতিটা key process করার পর array-টা দেখান।
Show Answerউত্তর দেখুন
Answer:
  • key = 4: 9 > 4 shifts right, insert 4 → [4, 9, 6, 2]
  • key = 6: 9 > 6 shifts, 4 < 6 stop, insert 6 → [4, 6, 9, 2]
  • key = 2: 9, 6, 4 all shift, insert 2 at front → [2, 4, 6, 9]
Total shifts = 1 + 1 + 3 = 5. The sorted left part grows by one each round.
Answer:
  • key = 4: 9 > 4 ডানে shift, 4 insert → [4, 9, 6, 2]
  • key = 6: 9 > 6 shift, 4 < 6 থামুন, 6 insert → [4, 6, 9, 2]
  • key = 2: 9, 6, 4 সব shift, 2 সামনে insert → [2, 4, 6, 9]
মোট shift = 1 + 1 + 3 = 5। Sorted বাম অংশ প্রতি round-এ একটা করে বাড়ে।
Q10. Trace binary search for key = 31 in [10, 14, 19, 26, 27, 31, 33, 35, 42]. Write lo, hi, mid at each step.
Q10. [10, 14, 19, 26, 27, 31, 33, 35, 42]-এ key = 31 খুঁজতে binary search trace করুন। প্রতিটা step-এ lo, hi, mid লিখুন।
Show Answerউত্তর দেখুন
Answer:
  • Step 1: lo=0, hi=8, mid=4 → a[4]=27. 27 < 31 → lo = 5.
  • Step 2: lo=5, hi=8, mid=6 → a[6]=33. 33 > 31 → hi = 5.
  • Step 3: lo=5, hi=5, mid=5 → a[5]=31. Found at index 5.
3 comparisons for 9 elements — matches \( \lceil \log_2 9 \rceil \approx 3.17 \to 4 \) worst case; here we got lucky at step 3.
Answer:
  • Step 1: lo=0, hi=8, mid=4 → a[4]=27। 27 < 31 → lo = 5।
  • Step 2: lo=5, hi=8, mid=6 → a[6]=33। 33 > 31 → hi = 5।
  • Step 3: lo=5, hi=5, mid=5 → a[5]=31। Index 5-এ পাওয়া গেল।
9 element-এ 3 comparison — worst case \( \lceil \log_2 9 \rceil \approx 3.17 \to 4 \)-এর সাথে মেলে; এখানে step 3-এই পেয়ে গেছি।
Q11. Show the first partition step of quick sort on [4, 9, 2, 7, 3] with the last element (3) as pivot. What is the array after partition, and which recursive calls follow?
Q11. [4, 9, 2, 7, 3]-এ শেষ element (3)-কে pivot ধরে quick sort-এর প্রথম partition step দেখান। Partition-এর পর array কেমন হবে, আর এরপর কোন recursive call হবে?
Show Answerউত্তর দেখুন
Answer: pivot = 3, i = −1.
  • j=0: 4 < 3? No. j=1: 9 < 3? No.
  • j=2: 2 < 3? Yes → i=0, swap a[0],a[2] → [2, 9, 4, 7, 3]
  • j=3: 7 < 3? No.
  • Final: swap pivot into i+1 = 1 → [2, 3, 4, 7, 9]. Pivot 3 is fixed at index 1.
Recursive calls: quickSort on left part [2] (indices 0..0, already 1 element) and on right part [4, 7, 9] (indices 2..4). By luck this array is already sorted after one partition, but the calls still run to confirm.
Answer: pivot = 3, i = −1।
  • j=0: 4 < 3? না। j=1: 9 < 3? না।
  • j=2: 2 < 3? হ্যাঁ → i=0, a[0],a[2] swap → [2, 9, 4, 7, 3]
  • j=3: 7 < 3? না।
  • শেষে: pivot-কে i+1 = 1-এ swap → [2, 3, 4, 7, 9]। Pivot 3 index 1-এ fix।
Recursive call: বাম অংশ [2] (index 0..0, একটাই element) আর ডান অংশ [4, 7, 9] (index 2..4)-এ quickSort। কাকতালীয়ভাবে এক partition-এই sorted, কিন্তু call-গুলো তবুও চলে নিশ্চিত করতে।
Q12. Coins are {1, 5, 6}. Amount = 10. (i) What does the greedy method (biggest coin first) give? (ii) Build the DP table dp[0..10] and find the minimum number of coins.
Q12. Coin {1, 5, 6}। Amount = 10। (i) Greedy method (আগে সবচেয়ে বড় coin) কী দেয়? (ii) dp[0..10] table বানিয়ে minimum coin সংখ্যা বের করুন।
Show Answerউত্তর দেখুন
Answer: (i) Greedy: take 6 (left 4), then 1+1+1+1 → total 5 coins. (ii) DP with dp[v] = 1 + min(dp[v−1], dp[v−5], dp[v−6]):
dp = [0, 1, 2, 3, 4, 1, 1, 2, 3, 4, 2]
Key cells: dp[5]=1 (coin 5), dp[6]=1 (coin 6), dp[10] = 1 + min(dp[9]=4, dp[5]=1, dp[4]=4) = 1 + 1 = 2 coins (5+5). Greedy (5 coins) is far from optimal (2 coins) — this coin system is not canonical, so DP is required.
Answer: (i) Greedy: 6 নিন (বাকি 4), তারপর 1+1+1+1 → মোট 5 coin। (ii) dp[v] = 1 + min(dp[v−1], dp[v−5], dp[v−6]) দিয়ে DP:
dp = [0, 1, 2, 3, 4, 1, 1, 2, 3, 4, 2]
গুরুত্বপূর্ণ cell: dp[5]=1 (coin 5), dp[6]=1 (coin 6), dp[10] = 1 + min(dp[9]=4, dp[5]=1, dp[4]=4) = 1 + 1 = 2 coin (5+5)। Greedy (5 coin) optimal (2 coin) থেকে অনেক দূরে — এই coin system canonical না, তাই DP লাগবে।
Q13. Characters and frequencies: p:10, q:15, r:30, s:45. Build the Huffman tree (show each merge), write the code of each character, and compute the total encoded bits.
Q13. Character আর frequency: p:10, q:15, r:30, s:45। Huffman tree বানান (প্রতিটা merge দেখান), প্রতিটা character-এর code লিখুন, আর মোট encoded bit বের করুন।
Show Answerউত্তর দেখুন
Answer:
  • Merge 1: p(10) + q(15) = n1(25). Left: {25, 30, 45}
  • Merge 2: n1(25) + r(30) = n2(55). Left: {45, 55}
  • Merge 3: s(45) + n2(55) = root(100).
Codes (left = 0, right = 1, s on the left of root): s = 0, r = 11, p = 100, q = 101.
Total bits = 45·1 + 30·2 + 10·3 + 15·3 = 45 + 60 + 30 + 45 = 180 bits. (Fixed 2-bit codes would need 100·2 = 200 bits, so Huffman saves 20.)
Answer:
  • Merge 1: p(10) + q(15) = n1(25)। বাকি: {25, 30, 45}
  • Merge 2: n1(25) + r(30) = n2(55)। বাকি: {45, 55}
  • Merge 3: s(45) + n2(55) = root(100)।
Code (বাম = 0, ডান = 1, root-এর বামে s): s = 0, r = 11, p = 100, q = 101।
মোট bit = 45·1 + 30·2 + 10·3 + 15·3 = 45 + 60 + 30 + 45 = 180 bit। (Fixed 2-bit code-এ লাগত 100·2 = 200 bit, তাই Huffman বাঁচালো 20।)
Q14. W = 4. Items: A(weight 2, value 3), B(weight 3, value 4), C(weight 1, value 2). Build the full 0/1 knapsack DP table and find the maximum value and the chosen items.
Q14. W = 4। Item: A(weight 2, value 3), B(weight 3, value 4), C(weight 1, value 2)। পুরো 0/1 knapsack DP table বানান, maximum value আর কোন item নেওয়া হলো বের করুন।
Show Answerউত্তর দেখুন
Answer: Rows: items added one by one; columns w = 0..4.
dp01234
no items00000
A (2,3)00333
+B (3,4)00344
+C (1,2)02356
Sample: dp[B row][4] = max(3, 4 + dp[A][1] = 4+0) = 4. dp[C row][4] = max(4, 2 + dp[B][3] = 2+4 = 6) = 6. dp[C row][3] = max(4, 2 + dp[B][2] = 2+3 = 5) = 5.
Max value = 6. Backtrack: C was taken (6 ≠ 4), left w = 3; B row at w=3 is 4 ≠ dp[A][3]=3 so B taken, left w = 0; A not taken. Chosen items: B and C (weight 3+1 = 4, value 4+2 = 6).
Answer: Row: item একটা একটা করে যোগ; column w = 0..4।
dp01234
কোনো item না00000
A (2,3)00333
+B (3,4)00344
+C (1,2)02356
হিসাব: dp[B row][4] = max(3, 4 + dp[A][1] = 4+0) = 4। dp[C row][4] = max(4, 2 + dp[B][3] = 2+4 = 6) = 6। dp[C row][3] = max(4, 2 + dp[B][2] = 2+3 = 5) = 5।
Max value = 6। Backtrack: C নেওয়া হয়েছে (6 ≠ 4), বাকি w = 3; w=3-এ B row-এর 4 ≠ dp[A][3]=3 তাই B নেওয়া, বাকি w = 0; A নেওয়া হয়নি। নেওয়া item: B আর C (weight 3+1 = 4, value 4+2 = 6)।
Q15. Graph: nodes S, A, B, C. Edges: S–A (7), S–B (2), B–A (3), A–C (1), B–C (8). Run Dijkstra from S with a step-by-step table, then also give the MST total weight by Kruskal. Are the shortest-path tree and the MST the same here?
Q15. Graph: node S, A, B, C। Edge: S–A (7), S–B (2), B–A (3), A–C (1), B–C (8)। S থেকে Dijkstra চালান step-by-step table-সহ, তারপর Kruskal দিয়ে MST-র মোট weight-ও দিন। এখানে shortest-path tree আর MST কি একই?
Show Answerউত্তর দেখুন
Answer: Dijkstra from S:
  • Init: dist[S]=0, A=∞, B=∞, C=∞.
  • Pick S(0): relax S–A → A=7; S–B → B=2.
  • Pick B(2): relax B–A → 2+3=5 < 7 → A=5; B–C → 2+8=10 → C=10.
  • Pick A(5): relax A–C → 5+1=6 < 10 → C=6.
  • Pick C(6): done.
Shortest distances: A=5 (S→B→A), B=2, C=6 (S→B→A→C). Shortest-path tree edges: S–B, B–A, A–C.
Kruskal: sorted edges A–C(1), S–B(2), B–A(3), S–A(7), B–C(8). Take A–C(1), S–B(2), B–A(3) — no cycles, 3 edges = V−1 → stop. MST weight = 1+2+3 = 6. Edges: {A–C, S–B, B–A}.
Yes — here they use the same edges. But this is a coincidence, not a rule: shortest-path trees and MSTs optimize different things and often differ.
Answer: S থেকে Dijkstra:
  • শুরু: dist[S]=0, A=∞, B=∞, C=∞।
  • S(0) নিন: relax S–A → A=7; S–B → B=2।
  • B(2) নিন: relax B–A → 2+3=5 < 7 → A=5; B–C → 2+8=10 → C=10।
  • A(5) নিন: relax A–C → 5+1=6 < 10 → C=6।
  • C(6) নিন: শেষ।
Shortest distance: A=5 (S→B→A), B=2, C=6 (S→B→A→C)। Shortest-path tree-র edge: S–B, B–A, A–C।
Kruskal: sorted edge A–C(1), S–B(2), B–A(3), S–A(7), B–C(8)। A–C(1), S–B(2), B–A(3) নিন — কোনো cycle নেই, 3টা edge = V−1 → থামুন। MST weight = 1+2+3 = 6। Edge: {A–C, S–B, B–A}।
হ্যাঁ — এখানে edge-গুলো একই। কিন্তু এটা কাকতালীয়, নিয়ম না: shortest-path tree আর MST আলাদা জিনিস optimize করে, প্রায়ই আলাদা হয়।
Q16. (Real exam style — April 2019 & 2024) Define the classes P and NP with one example each. Then draw a diagram showing the relationship between P, NP, NP-Hard and NP-Complete (assume P ≠ NP).
Q16. (Real exam style — April 2019 & 2024) P আর NP class define করুন, প্রতিটার একটা করে example দিন। তারপর P, NP, NP-Hard আর NP-Complete-এর relationship দেখিয়ে একটা diagram আঁকুন (ধরুন P ≠ NP)।
Show Answerউত্তর দেখুন
Answer:
  • P = problems that can be solved in polynomial time (like \( O(n^2) \)). Example: sorting an array.
  • NP = problems whose given solution can be verified in polynomial time (finding it may be hard, checking is easy). Example: SAT — checking a given true/false assignment is easy.
  • NP-Complete = problems that are in NP AND every NP problem reduces to them in polynomial time (the hardest inside NP). Example: 3-SAT.
  • NP-Hard = at least as hard as everything in NP, but not required to be in NP. Example: TSP optimization, halting problem.
Diagram: draw a big NP oval with a smaller P oval fully inside it. Draw an NP-Hard oval that overlaps NP on one side and sticks outside on the other. Label the overlap region NP ∩ NP-Hard as NP-Complete. Key facts to write beside it: \( P \subseteq NP \), NP-Complete = NP ∩ NP-Hard, and whether P = NP is an open question (if P = NP, then P, NP and NP-Complete collapse into one set). This matches the SVG figure in Section 8.
Answer:
  • P = যে problem polynomial time-এ (যেমন \( O(n^2) \)) solve করা যায়। Example: array sort করা।
  • NP = যে problem-এর দেওয়া solution polynomial time-এ verify করা যায় (খুঁজে বের করা কঠিন হতে পারে, check করা সহজ)। Example: SAT — দেওয়া true/false assignment check করা সহজ।
  • NP-Complete = যে problem NP-তে আছে এবং NP-এর প্রতিটা problem polynomial time-এ এতে reduce হয় (NP-এর ভেতরের সবচেয়ে কঠিন)। Example: 3-SAT।
  • NP-Hard = NP-এর সবকিছুর মতো বা তার চেয়ে কঠিন, কিন্তু NP-তে থাকা জরুরি না। Example: TSP optimization, halting problem।
Diagram: একটা বড় NP oval আঁকুন, তার পুরো ভেতরে ছোট P oval। একটা NP-Hard oval আঁকুন যা এক পাশে NP-এর সাথে overlap করে, অন্য পাশে বাইরে বেরিয়ে থাকে। NP ∩ NP-Hard overlap অংশে label দিন NP-Complete। পাশে লিখুন: \( P \subseteq NP \), NP-Complete = NP ∩ NP-Hard, আর P = NP কিনা সেটা open question (P = NP হলে P, NP আর NP-Complete এক set হয়ে যায়)। এটা Section 8-এর SVG figure-এর মতোই।
Q17. (Real exam style — April 2024) What is the difference between an approximation algorithm and a heuristic algorithm? Give one example of each, and say which one you would trust more when a quality guarantee is needed.
Q17. (Real exam style — April 2024) Approximation algorithm আর heuristic algorithm-এর মধ্যে পার্থক্য কী? প্রতিটার একটা করে example দিন, আর quality guarantee দরকার হলে কোনটার উপর বেশি ভরসা করবেন বলুন।
Show Answerউত্তর দেখুন
Answer: An approximation algorithm comes with a provable mathematical bound: its answer is guaranteed to be within a known factor of the optimal. Example: the 2-approximation for vertex cover — the cover it returns is never more than 2× the optimal size, and this is proven for every input. A heuristic algorithm has no guarantee at all: it uses a practical rule of thumb that usually works well, but on some inputs it can give a very bad answer, and we cannot say in advance when. Examples: hill climbing (can get stuck at a local optimum), the heuristic function in A* search. When a quality guarantee is needed, trust the approximation algorithm — its worst case is bounded on paper; a heuristic's worst case is unbounded.
Answer: Approximation algorithm-এর সাথে একটা provable mathematical bound থাকে: এর answer optimal-এর একটা জানা factor-এর মধ্যে থাকবে — এটা guaranteed। Example: vertex cover-এর 2-approximation — এর দেওয়া cover কখনো optimal size-এর 2× এর বেশি হয় না, আর এটা প্রতিটা input-এর জন্য proven। Heuristic algorithm-এর কোনো guarantee নেই: এটা একটা practical rule of thumb ব্যবহার করে যা সাধারণত ভালো কাজ করে, কিন্তু কোনো কোনো input-এ খুব খারাপ answer দিতে পারে, কখন দেবে আগে থেকে বলা যায় না। Example: hill climbing (local optimum-এ আটকে যেতে পারে), A* search-এর heuristic function। Quality guarantee দরকার হলে approximation algorithm-এর উপর ভরসা করুন — এর worst case কাগজে-কলমে bounded; heuristic-এর worst case unbounded।
Q18. (Real exam style — October 2017) An algorithm for multiplying two square matrices takes 21 steps to multiply two 7×7 matrices. How many steps will it take to multiply two n×n matrices? Verify your formula for n = 14.
Q18. (Real exam style — October 2017) দুটো square matrix গুণ করার একটা algorithm দুটো 7×7 matrix গুণ করতে 21 step নেয়। দুটো n×n matrix গুণ করতে কত step লাগবে? n = 14 দিয়ে আপনার formula verify করুন।
Show Answerউত্তর দেখুন
Answer: \( \frac{3n^3}{49} \) steps. Standard matrix multiplication is \( \Theta(n^3) \), so steps \( = c \cdot n^3 \). Use the given data point: \( 21 = c \cdot 7^3 = 343c \Rightarrow c = \frac{21}{343} = \frac{3}{49} \). So \( \text{steps}(n) = \frac{3n^3}{49} = 21\left(\frac{n}{7}\right)^3 \). Verify (n = 14): \( \frac{3 \cdot 14^3}{49} = \frac{3 \cdot 2744}{49} = 168 \). Cross-check by scaling: doubling n in an \( n^3 \) algorithm multiplies steps by \( 2^3 = 8 \), and \( 21 \times 8 = 168 \). It matches, so the formula is correct.
Answer: \( \frac{3n^3}{49} \) step। Standard matrix multiplication হলো \( \Theta(n^3) \), তাই step \( = c \cdot n^3 \)। দেওয়া data point ব্যবহার করুন: \( 21 = c \cdot 7^3 = 343c \Rightarrow c = \frac{21}{343} = \frac{3}{49} \)। তাহলে \( \text{steps}(n) = \frac{3n^3}{49} = 21\left(\frac{n}{7}\right)^3 \)। Verify (n = 14): \( \frac{3 \cdot 14^3}{49} = \frac{3 \cdot 2744}{49} = 168 \)। Scaling দিয়ে cross-check: \( n^3 \) algorithm-এ n double করলে step \( 2^3 = 8 \) গুণ হয়, আর \( 21 \times 8 = 168 \)। মিলে গেছে, তাই formula সঠিক।
Q19. (Real exam style — October 2018) A project has 5 tasks. T1 (duration 4) and T2 (duration 3) have no dependency. T3 (duration 5) depends on T1. T4 (duration 2) depends on T1 and T2. T5 (duration 4) depends on T3 and T4. Tasks with no dependency between them can run in parallel. Find the minimum completion time of the project and the critical path.
Q19. (Real exam style — October 2018) একটা project-এ 5টা task আছে। T1 (duration 4) আর T2 (duration 3)-এর কোনো dependency নেই। T3 (duration 5) depend করে T1-এর উপর। T4 (duration 2) depend করে T1 আর T2-এর উপর। T5 (duration 4) depend করে T3 আর T4-এর উপর। যাদের মধ্যে dependency নেই তারা parallel-এ চলতে পারে। Project-এর minimum completion time আর critical path বের করুন।
Show Answerউত্তর দেখুন
Answer: 13 units, critical path T1 → T3 → T5. Do a forward pass (ES = max EF of dependencies, EF = ES + duration):
  • T1: ES = 0, EF = 0 + 4 = 4
  • T2: ES = 0, EF = 0 + 3 = 3
  • T3: ES = EF(T1) = 4, EF = 4 + 5 = 9
  • T4: ES = max(EF(T1), EF(T2)) = max(4, 3) = 4, EF = 4 + 2 = 6
  • T5: ES = max(EF(T3), EF(T4)) = max(9, 6) = 9, EF = 9 + 4 = 13
Minimum completion time = max EF = 13. Trace the max back: T5 waited for T3 (9), T3 waited for T1 (4). So the critical path is T1 → T3 → T5 with length 4 + 5 + 4 = 13 — the longest path in the task DAG. The other chain T2 → T4 → T5 finishes T4 by 6, well before T3's 9, so T2 and T4 have slack and do not delay anything.
Answer: 13 unit, critical path T1 → T3 → T5। Forward pass করুন (ES = dependency-গুলোর max EF, EF = ES + duration):
  • T1: ES = 0, EF = 0 + 4 = 4
  • T2: ES = 0, EF = 0 + 3 = 3
  • T3: ES = EF(T1) = 4, EF = 4 + 5 = 9
  • T4: ES = max(EF(T1), EF(T2)) = max(4, 3) = 4, EF = 4 + 2 = 6
  • T5: ES = max(EF(T3), EF(T4)) = max(9, 6) = 9, EF = 9 + 4 = 13
Minimum completion time = max EF = 13। Max-টা পিছনে ট্রেস করুন: T5 অপেক্ষা করেছে T3 (9)-এর জন্য, T3 অপেক্ষা করেছে T1 (4)-এর জন্য। তাই critical path হলো T1 → T3 → T5, length 4 + 5 + 4 = 13 — task DAG-এর longest path। অন্য chain T2 → T4 → T5-এ T4 শেষ হয় 6-এ, T3-এর 9-এর অনেক আগে, তাই T2 আর T4-এর slack আছে, কাউকে delay করে না।