From 27363e2d84113f5753a9530f43511cb4600b15ca Mon Sep 17 00:00:00 2001 From: Faisal Misbah Date: Tue, 26 Aug 2025 16:34:08 +0500 Subject: [PATCH] Add notes for graph and heaps [session:1] --- Level 0/11 Graph/01. BFS Traversal/code.cpp | 24 ++ Level 0/11 Graph/01. BFS Traversal/read.md | 25 ++ Level 0/11 Graph/02. DFS Traversal/code.cpp | 19 ++ Level 0/11 Graph/02. DFS Traversal/read.md | 27 ++ Level 0/11 Graph/README.md | 261 ++++++++++++++++++ .../01. Merge K Sorted Arrays (GFG)/code.cpp | 20 ++ .../01. Merge K Sorted Arrays (GFG)/read.md | 18 ++ .../code.cpp | 27 ++ .../read.md | 18 ++ .../code.cpp | 27 ++ .../read.md | 18 ++ Level 0/12 Heap + K-Way Merge/README.md | 103 +++++++ .../heap_introduction.md | 47 ++++ 13 files changed, 634 insertions(+) create mode 100644 Level 0/11 Graph/01. BFS Traversal/code.cpp create mode 100644 Level 0/11 Graph/01. BFS Traversal/read.md create mode 100644 Level 0/11 Graph/02. DFS Traversal/code.cpp create mode 100644 Level 0/11 Graph/02. DFS Traversal/read.md create mode 100644 Level 0/11 Graph/README.md create mode 100644 Level 0/12 Heap + K-Way Merge/01. Merge K Sorted Arrays (GFG)/code.cpp create mode 100644 Level 0/12 Heap + K-Way Merge/01. Merge K Sorted Arrays (GFG)/read.md create mode 100644 Level 0/12 Heap + K-Way Merge/02. 0088 - Merge Sorted Array (LC)/code.cpp create mode 100644 Level 0/12 Heap + K-Way Merge/02. 0088 - Merge Sorted Array (LC)/read.md create mode 100644 Level 0/12 Heap + K-Way Merge/03. 0023 - Merge K Sorted Lists (LC)/code.cpp create mode 100644 Level 0/12 Heap + K-Way Merge/03. 0023 - Merge K Sorted Lists (LC)/read.md create mode 100644 Level 0/12 Heap + K-Way Merge/README.md create mode 100644 Level 0/12 Heap + K-Way Merge/heap_introduction.md diff --git a/Level 0/11 Graph/01. BFS Traversal/code.cpp b/Level 0/11 Graph/01. BFS Traversal/code.cpp new file mode 100644 index 0000000..f21dc14 --- /dev/null +++ b/Level 0/11 Graph/01. BFS Traversal/code.cpp @@ -0,0 +1,24 @@ +#include +#include +using namespace std; + +vector bfsOfGraph(int V, vector adj[]) { + vector order; + vector visited(V, 0); + queue q; + q.push(0); + visited[0] = 1; + while(!q.empty()){ + int node = q.front(); q.pop(); + order.push_back(node); + for(int neighbor : adj[node]){ + if(!visited[neighbor]){ + visited[neighbor] = 1; + q.push(neighbor); + } + } + } + return order; +} + + diff --git a/Level 0/11 Graph/01. BFS Traversal/read.md b/Level 0/11 Graph/01. BFS Traversal/read.md new file mode 100644 index 0000000..77e0d5a --- /dev/null +++ b/Level 0/11 Graph/01. BFS Traversal/read.md @@ -0,0 +1,25 @@ +## Problem: BFS Traversal of Graph + +You are given an undirected graph with V vertices labeled 0 to V-1 and E edges. +Perform a Breadth-First Search (BFS) starting from node 0 and output the order +in which the nodes are visited. + +Input: +- First line: two integers V (number of vertices) and E (number of edges) +- Next E lines: two integers u v indicating an undirected edge between u and v + +Output: +- Print the BFS order starting from node 0, space-separated on one line. + +Example: +Input +5 4 +0 1 +0 2 +1 3 +2 4 + +Output +0 1 2 3 4 + + diff --git a/Level 0/11 Graph/02. DFS Traversal/code.cpp b/Level 0/11 Graph/02. DFS Traversal/code.cpp new file mode 100644 index 0000000..76b4272 --- /dev/null +++ b/Level 0/11 Graph/02. DFS Traversal/code.cpp @@ -0,0 +1,19 @@ +#include +using namespace std; + +void dfsRec(int node, vector adj[], vector& visited, vector& order){ + visited[node] = 1; + order.push_back(node); + for(int neighbor : adj[node]){ + if(!visited[neighbor]) dfsRec(neighbor, adj, visited, order); + } +} + +vector dfsOfGraph(int V, vector adj[]) { + vector order; + vector visited(V, 0); + dfsRec(0, adj, visited, order); + return order; +} + + diff --git a/Level 0/11 Graph/02. DFS Traversal/read.md b/Level 0/11 Graph/02. DFS Traversal/read.md new file mode 100644 index 0000000..14704ee --- /dev/null +++ b/Level 0/11 Graph/02. DFS Traversal/read.md @@ -0,0 +1,27 @@ +## Problem: DFS Traversal of Graph + +You are given an undirected graph with V vertices labeled 0 to V-1 and E edges. +Perform a Depth-First Search (DFS) starting from node 0 and output the order +in which the nodes are visited. + +Input: +- First line: two integers V (number of vertices) and E (number of edges) +- Next E lines: two integers u v indicating an undirected edge between u and v + +Output: +- Print the DFS order starting from node 0, space-separated on one line. + +Note: If multiple neighbors are available, visiting order can vary. + +Example: +Input +5 4 +0 1 +0 2 +1 3 +2 4 + +Output +0 1 3 2 4 + + diff --git a/Level 0/11 Graph/README.md b/Level 0/11 Graph/README.md new file mode 100644 index 0000000..5a8c2ef --- /dev/null +++ b/Level 0/11 Graph/README.md @@ -0,0 +1,261 @@ +## What is Graph Data Structure? + +Graph is a non-linear data structure of vertices (nodes) and edges (connections). Formally, G(V, E) where V is set of vertices and E is set of edges. + +Think of a football match: players are nodes, passes/tackles are edges. The web of interactions is a graph. + +--- + +### Components of a Graph +- Vertices (nodes) +- Edges (connections) + +### Types of Graphs +- Undirected vs Directed +- Connected vs Disconnected +- Cycle Graph, Cyclic Graph, Directed Acyclic Graph (DAG) +- Weighted Graph (directed/undirected) + +--- + +### Representation of Graph +- Adjacency Matrix +- Adjacency List + +Adjacency Matrix (skeleton) +```cpp +#include +using namespace std; + +void displayMatrix(const vector>& mat){ + int V = (int)mat.size(); + for(int i=0;i> mat = ... // fill matrix + cout << "Adjacency Matrix Representation\n"; + displayMatrix(mat); +} +``` + +Adjacency List (template) +```cpp +#include +using namespace std; + +// Undirected, unweighted +vector> buildGraph(int n, const vector>& edges) { + vector> g(n); + for (auto [u, v] : edges) { + g[u].push_back(v); + g[v].push_back(u); + } + return g; +} + +// Directed, weighted +vector>> buildWeightedDigraph(int n, const vector>& edges) { + vector>> g(n); + for (auto [u, v, w] : edges) { + g[u].push_back({v, w}); + } + return g; +} +``` + +--- + +### Traversals +- BFS (level-by-level, shortest path in unweighted graphs) +```cpp +vector bfsShortestPath(const vector>& g, int src) { + int n = (int)g.size(); + vector dist(n, -1); + queue q; q.push(src); dist[src] = 0; + while (!q.empty()) { + int u = q.front(); q.pop(); + for (int v : g[u]) if (dist[v] == -1) { + dist[v] = dist[u] + 1; + q.push(v); + } + } + return dist; +} +``` + +- DFS (deep exploration, detect cycles, topological sort helper) +```cpp +void dfsUtil(int u, const vector>& g, vector& vis) { + vis[u] = 1; + for (int v : g[u]) if (!vis[v]) dfsUtil(v, g, vis); +} +``` + +Edge cases for traversals +- Disconnected graphs → run BFS/DFS from all unvisited nodes. +- Self-loops / parallel edges → handle naturally with visited arrays. +- Large graphs → iterative DFS to avoid recursion stack overflow. + +--- + +### Classic problems and templates + +Topological Sort (DAG only) +```cpp +vector topoKahn(const vector>& g) { + int n = (int)g.size(); + vector indeg(n, 0); + for (int u = 0; u < n; ++u) for (int v : g[u]) indeg[v]++; + queue q; + for (int i = 0; i < n; ++i) if (indeg[i] == 0) q.push(i); + vector order; + while (!q.empty()) { + int u = q.front(); q.pop(); order.push_back(u); + for (int v : g[u]) if (--indeg[v] == 0) q.push(v); + } + if ((int)order.size() != n) return {}; // cycle exists + return order; +} +``` + +Cycle detection +```cpp +// Undirected: if neighbor visited and not parent → cycle +bool hasCycleUndirected(int n, const vector>& g) { + vector vis(n, 0); + function dfs = [&](int u, int parent) { + vis[u] = 1; + for (int v : g[u]) { + if (!vis[v]) { if (dfs(v, u)) return true; } + else if (v != parent) return true; + } + return false; + }; + for (int i = 0; i < n; ++i) if (!vis[i] && dfs(i, -1)) return true; + return false; +} + +// Directed: DFS colors (0=unseen,1=stack,2=done) → back-edge means cycle +bool hasCycleDirected(int n, const vector>& g) { + vector color(n, 0); + function dfs = [&](int u) { + color[u] = 1; + for (int v : g[u]) { + if (color[v] == 0) { if (dfs(v)) return true; } + else if (color[v] == 1) return true; + } + color[u] = 2; return false; + }; + for (int i = 0; i < n; ++i) if (color[i] == 0 && dfs(i)) return true; + return false; +} +``` + +Shortest paths +```cpp +// Dijkstra (non-negative weights) +vector dijkstra(const vector>>& g, int src) { + const long long INF = (1LL<<60); + int n = (int)g.size(); + vector dist(n, INF); + using P = pair; // dist, node + priority_queue, greater

> pq; + dist[src] = 0; pq.push({0, src}); + while (!pq.empty()) { + auto [d, u] = pq.top(); pq.pop(); + if (d != dist[u]) continue; + for (auto [v, w] : g[u]) if (dist[v] > d + w) { + dist[v] = d + w; + pq.push({dist[v], v}); + } + } + return dist; +} + +// BFS for unweighted graphs is shortest path in edges (see bfsShortestPath above). +``` + +Minimum Spanning Tree (MST) +```cpp +// Kruskal with DSU +struct DSU { + vector p, r; + DSU(int n): p(n), r(n,0){ iota(p.begin(), p.end(), 0);} + int find(int x){ return p[x]==x?x:p[x]=find(p[x]); } + bool unite(int a,int b){ a=find(a); b=find(b); if(a==b) return false; if(r[a]> edges) { + sort(edges.begin(), edges.end(), [](auto &a, auto &b){return get<2>(a) < get<2>(b);} ); + DSU dsu(n); long long cost = 0; int used = 0; + for (auto [u,v,w] : edges) if (dsu.unite(u,v)) { cost += w; used++; } + if (used != n-1) return -1; // not connected + return cost; +} +``` + +Strongly Connected Components (SCC) – Kosaraju +```cpp +vector kosarajuSCC(const vector>& g) { + int n = (int)g.size(); + vector> gr(n); + for (int u = 0; u < n; ++u) for (int v : g[u]) gr[v].push_back(u); + vector vis(n,0), order; + function dfs1 = [&](int u){ vis[u]=1; for(int v: g[u]) if(!vis[v]) dfs1(v); order.push_back(u); }; + for (int i=0;i comp(n,-1); int cid=0; + function dfs2 = [&](int u){ comp[u]=cid; for(int v: gr[u]) if(comp[v]==-1) dfs2(v); }; + for (int i=n-1;i>=0;--i){ int u=order[i]; if(comp[u]==-1){ dfs2(u); cid++; } } + return comp; // comp[u] = component id +} +``` + +--- + +### Edge cases (collective) +- Disconnected graphs; isolated nodes. +- Self-loops and multi-edges. +- Negative weights (use Bellman-Ford; Dijkstra breaks). +- Very large graphs: prefer adjacency lists; avoid recursion. +- Graph indexing (0-based vs 1-based) consistency. + +--- + +### Common interview questions +- BFS/DFS traversals; number of connected components. +- Detect cycle (directed/undirected). +- Topological sort and prerequisites problems. +- Shortest path (BFS, Dijkstra, Bellman-Ford), network delay time. +- MST (Kruskal/Prim), connecting points with minimum cost. +- SCCs (Kosaraju/Tarjan), course schedule variants. + +--- + +### Important snippets (quick copy) +- Build undirected/unweighted graph (above) +- BFS shortest path (above) +- Dijkstra template (above) +- DSU + Kruskal (above) +- Kahn’s topological sort (above) + +--- + +### Applications +- Data structures: union-find for connectivity; priority queues in Dijkstra/Prim. +- Algorithms: scheduling (topo), routing (shortest paths), clustering (MST), dependency resolution. + +--- + +## 💡 Suggestions + +- Start with BFS/DFS on small graphs; draw by hand. +- Practice templates until you can write them from memory. +- Know when to use BFS vs Dijkstra vs Bellman-Ford. +- Watch out for indexing and visited resets between test cases. +- Time-box to 30–40 minutes; keep short notes and test small cases. + + diff --git a/Level 0/12 Heap + K-Way Merge/01. Merge K Sorted Arrays (GFG)/code.cpp b/Level 0/12 Heap + K-Way Merge/01. Merge K Sorted Arrays (GFG)/code.cpp new file mode 100644 index 0000000..6ab1eda --- /dev/null +++ b/Level 0/12 Heap + K-Way Merge/01. Merge K Sorted Arrays (GFG)/code.cpp @@ -0,0 +1,20 @@ +#include +#include +using namespace std; + +vector mergeKArrays(vector> arr, int K) { + struct Item { int value, i, j; }; + struct Cmp { bool operator()(const Item& a, const Item& b) const { return a.value > b.value; } }; + priority_queue, Cmp> pq; + vector result; + for(int i = 0; i < K; i++) if(!arr[i].empty()) pq.push({arr[i][0], i, 0}); + while(!pq.empty()){ + auto cur = pq.top(); pq.pop(); + result.push_back(cur.value); + int nj = cur.j + 1; + if(nj < (int)arr[cur.i].size()) pq.push({arr[cur.i][nj], cur.i, nj}); + } + return result; +} + + diff --git a/Level 0/12 Heap + K-Way Merge/01. Merge K Sorted Arrays (GFG)/read.md b/Level 0/12 Heap + K-Way Merge/01. Merge K Sorted Arrays (GFG)/read.md new file mode 100644 index 0000000..4b60f9d --- /dev/null +++ b/Level 0/12 Heap + K-Way Merge/01. Merge K Sorted Arrays (GFG)/read.md @@ -0,0 +1,18 @@ +## Problem: Merge K Sorted Arrays + +You are given K sorted arrays. Merge them into a single sorted array. + +Input: +- First line: integer K (number of arrays) +- For each i from 1..K: + - First an integer n (size of array i) + - Then n integers in non-decreasing order + +Output: +- All elements of the K arrays merged into one sorted array, space-separated. + +Hint: +- Keep a min-heap of the current first unused element from each array. +- Each time, remove the smallest and insert the next element from the same array. + + diff --git a/Level 0/12 Heap + K-Way Merge/02. 0088 - Merge Sorted Array (LC)/code.cpp b/Level 0/12 Heap + K-Way Merge/02. 0088 - Merge Sorted Array (LC)/code.cpp new file mode 100644 index 0000000..db377ba --- /dev/null +++ b/Level 0/12 Heap + K-Way Merge/02. 0088 - Merge Sorted Array (LC)/code.cpp @@ -0,0 +1,27 @@ +#include +using namespace std; + +void merge(vector& nums1, int m, vector& nums2, int n) { + int i = m - 1, j = n - 1, k = m + n - 1; + while(i >= 0 && j >= 0){ + if(nums1[i] > nums2[j]){ nums1[k] = nums1[i]; i--; } + else { nums1[k] = nums2[j]; j--; } + k--; + } + while(j >= 0){ nums1[k] = nums2[j]; j--; k--; } +} + +int main(){ + ios::sync_with_stdio(false); + cin.tie(nullptr); + int m,n; if(!(cin>>m>>n)) return 0; + vector a(m+n), b(n); + for(int i=0;i>a[i]; + for(int i=0;i>b[i]; + merge(a,m,b,n); + for(int x:a) cout< +#include +using namespace std; + +struct ListNode { + int val; + ListNode *next; + ListNode() : val(0), next(nullptr) {} + ListNode(int x) : val(x), next(nullptr) {} + ListNode(int x, ListNode *next) : val(x), next(next) {} +}; + + +ListNode* mergeKLists(vector& lists) { + struct Cmp { bool operator()(ListNode* a, ListNode* b) const { return a->val > b->val; } }; + priority_queue, Cmp> pq; + for (auto node : lists) if (node) pq.push(node); + ListNode dummy(0), *tail = &dummy; + while (!pq.empty()) { + ListNode* cur = pq.top(); pq.pop(); + tail->next = cur; tail = cur; + if (cur->next) pq.push(cur->next); + } + return dummy.next; +} + + diff --git a/Level 0/12 Heap + K-Way Merge/03. 0023 - Merge K Sorted Lists (LC)/read.md b/Level 0/12 Heap + K-Way Merge/03. 0023 - Merge K Sorted Lists (LC)/read.md new file mode 100644 index 0000000..d3bd655 --- /dev/null +++ b/Level 0/12 Heap + K-Way Merge/03. 0023 - Merge K Sorted Lists (LC)/read.md @@ -0,0 +1,18 @@ +## Problem: Merge K Sorted Linked Lists + +You are given k linked lists, each sorted in non-decreasing order. Merge all the +linked lists into one sorted linked list and return the head of the merged list. + +Input: +- First line: integer k (number of lists) +- For each i from 1..k: + - First an integer n (length of list i) + - Then n integers in non-decreasing order + +Output: +- The merged linked list values in non-decreasing order, space-separated. + +Hint: +- Use a min-heap of ListNode* by value. Pop the smallest, append to answer, and push its next. + + diff --git a/Level 0/12 Heap + K-Way Merge/README.md b/Level 0/12 Heap + K-Way Merge/README.md new file mode 100644 index 0000000..60121d6 --- /dev/null +++ b/Level 0/12 Heap + K-Way Merge/README.md @@ -0,0 +1,103 @@ +## Section 1: Intuition & Real-Life Analogy + +### 🔍 What is K-way Merge? +K-way merge means merging K sorted arrays/lists into one sorted array. It generalizes the 2-way merge used in merge sort. + +### Real-Life Analogy: Grocery Checkout Lines +Imagine K checkout counters, each with customers already in arrival order. You want one global queue that preserves arrival order across all counters. A min-heap helps always pick the earliest among the K current fronts. + +--- + +## Section 2: Basic Implementation Using Min-Heap + +### Core Idea +Maintain a min-heap of the current head from each list. Pop the smallest, append to result, then push the next from that list. + +```cpp +#include +using namespace std; + +struct Node { int value, listIndex, elementIndex; }; +struct Cmp { bool operator()(const Node& a, const Node& b) const { return a.value > b.value; } }; + +vector mergeK(const vector>& lists) { + priority_queue, Cmp> pq; + vector ans; + for (int i = 0; i < (int)lists.size(); ++i) if (!lists[i].empty()) pq.push({lists[i][0], i, 0}); + while (!pq.empty()) { + auto cur = pq.top(); pq.pop(); + ans.push_back(cur.value); + int j = cur.elementIndex + 1; + if (j < (int)lists[cur.listIndex].size()) pq.push({lists[cur.listIndex][j], cur.listIndex, j}); + } + return ans; +} +``` + +--- + +## Section 3: Generalizing K-Way Merge + +### What Can We Merge? +- Sorted arrays +- Sorted linked lists +- Sorted file streams + +--- + +## Section 4: Common Interview Variants + +1. Merge K Sorted Linked Lists (LeetCode #23) +2. External Sorting (merge sorted chunks from disk) +3. Streaming Merge (merge K logs/streams in real time) + +### Trade-offs + +| Strategy | Time Complexity | Space Complexity | Use Case | +| --- | --- | --- | --- | +| Full sort each time | O(N log N) | O(N) | Static data | +| Heap-based merge | O(N log K) | O(K) | Streaming/partial merge | + +### Performance +- Time: O(N log K) +- Space: O(K) + +--- + +## Level 1: Foundation – Arrays & Heaps + +| Problem | Platform | Focus | +| --- | --- | --- | +| Merge K Sorted Arrays | GeeksforGeeks | Classic K-way merge with arrays | +| Smallest Range Covering Elements from K Lists | LeetCode #632 | Min-heap + window tracking | +| Kth Smallest Element in a Sorted Matrix | LeetCode #378 | Min-heap with matrix coordinates | + +--- + +## Level 2: Linked Lists + +| Problem | Platform | Focus | +| --- | --- | --- | +| Merge K Sorted Linked Lists | LeetCode #23 | Min-heap with ListNode* | +| Sort a Linked List | LeetCode #148 | Merge sort (2-way) | +| Merge Two Sorted Lists | LeetCode #21 | Base case for recursion/merge logic | + +--- + +## Level 3: Advanced + +| Problem | Platform | Focus | +| --- | --- | --- | +| Kth Largest Element in a Stream | LeetCode #703 | Streaming + heap maintenance | +| Merge Intervals | LeetCode #56 | Interval merging | +| Top K Frequent Elements | LeetCode #347 | Frequency map + heap | + +--- + +## Problems (direct links) + +- https://www.geeksforgeeks.org/problems/merge-k-sorted-arrays/1 +- https://leetcode.com/problems/merge-sorted-array/description/ +- https://leetcode.com/problems/merge-k-sorted-lists/ + + diff --git a/Level 0/12 Heap + K-Way Merge/heap_introduction.md b/Level 0/12 Heap + K-Way Merge/heap_introduction.md new file mode 100644 index 0000000..0bf2a1c --- /dev/null +++ b/Level 0/12 Heap + K-Way Merge/heap_introduction.md @@ -0,0 +1,47 @@ +## Heaps for Beginners (Quick Intro) + +### What is a Heap? +- A heap is a special tree-like structure where the top element is always the most important. + - Min-heap: smallest number stays on top. + - Max-heap: largest number stays on top. + +### Real-life idea +- Hospital: most critical patient first → like picking the top from a heap. + +### Why use a heap? +- You need quick access to the smallest/largest item while data keeps coming. + +### Basic operations +- Peek (see top): O(1) +- Push (insert): O(log N) +- Pop (remove top): O(log N) + +### How it’s stored +- Usually as an array. For index i: left = 2*i+1, right = 2*i+2. + +### C++ +```cpp +#include +using namespace std; + +int main(){ + // Min-heap: smallest on top + priority_queue, greater> minH; + minH.push(5); minH.push(2); minH.push(8); + cout << minH.top() << "\n"; // 2 + + // Max-heap: largest on top + priority_queue maxH; + maxH.push(5); maxH.push(2); maxH.push(8); + cout << maxH.top() << "\n"; // 8 +} +``` + +### Common uses +- Top K elements, K-way merge, running median, Dijkstra’s shortest path. + +### Tips +- Use `greater` to make a min-heap in C++. +- Don’t pop from an empty heap; check size first. + +