Algorithm Templates: Data Structures & Core Algorithms
This page is your toolbox of essential data structures for LeetCode. Each template is self-contained C++ you can copy directly into your solution. They range from beginner-friendly (binary search, prefix sum) to advanced (segment tree, sparse table) — start with what you need and come back for more as you level up.
This is the foundation. These data structures are the building blocks that other templates (DFS, BFS, DP) build on. Master binary search and prefix sums first, then work outward — you’ll see them appear inside graph, tree, and dynamic programming solutions.
Contents
- Binary Search (Bounds)
- Prefix Sum & Difference Array
- Monotonic Stack
- Monotonic Queue
- Heap / Priority Queue
- Union-Find (DSU)
- Trie
- Segment Tree
- Fenwick Tree (BIT)
- Sparse Table (Range Min/Max)
Binary Search (Bounds)
When to use: The input is sorted (or the answer space is monotonic) and you need to find a boundary — first element ≥ x, last element ≤ x, or the minimum/maximum value satisfying a condition.
Half-open range [lo, hi). Use when you need first ≥ x (lower_bound) or first > x (upper_bound).
// First index where a[i] >= x (lower_bound)
int lower_bound(const vector<int>& a, int x) {
int lo = 0, hi = a.size();
while (lo < hi) {
int mid = lo + (hi - lo) / 2;
if (a[mid] < x) lo = mid + 1;
else hi = mid;
}
return lo;
}
// First index where a[i] > x (upper_bound)
int upper_bound(const vector<int>& a, int x) {
int lo = 0, hi = a.size();
while (lo < hi) {
int mid = lo + (hi - lo) / 2;
if (a[mid] <= x) lo = mid + 1;
else hi = mid;
}
return lo;
}
// Binary search on answer: smallest x in [lo, hi] such that ok(x)
template<class F>
int bsearch_ans(int lo, int hi, F ok) {
while (lo < hi) {
int mid = lo + (hi - lo) / 2;
if (ok(mid)) hi = mid;
else lo = mid + 1;
}
return lo;
}
| ID | Title | Link |
|---|---|---|
| 34 | Find First and Last Position | Link |
| 35 | Search Insert Position | Link |
| 875 | Koko Eating Bananas | Link |
Prefix Sum & Difference Array
When to use: You need to answer many “sum of subarray [l, r]” queries, or apply the same increment to many ranges efficiently.
Prefix sum: range sum in O(1). Difference array: range add in O(1), then one prefix sum to recover.
// Prefix sum: ps[i] = a[0]+...+a[i-1], sum(l,r) = ps[r+1]-ps[l]
vector<long long> prefix(const vector<int>& a) {
vector<long long> ps(a.size() + 1);
for (int i = 0; i < (int)a.size(); i++) ps[i + 1] = ps[i] + a[i];
return ps;
}
// Difference array: for [l,r] += d do diff[l]+=d, diff[r+1]-=d; then partial_sum(diff) = values
void range_add(vector<long long>& diff, int l, int r, long long d) {
diff[l] += d;
if (r + 1 < (int)diff.size()) diff[r + 1] -= d;
}
// After all updates: partial_sum(diff.begin(), diff.end(), diff.begin());
| ID | Title | Link |
|---|---|---|
| 560 | Subarray Sum Equals K | Link |
| 1109 | Corporate Flight Bookings | Link |
| 1094 | Car Pooling | Link |
Monotonic Stack
When to use: You need “next greater element”, “next smaller element”, “largest rectangle in histogram”, or any problem where each element is compared to its neighbors in one direction.
Maintain indices with strictly increasing (or decreasing) values. Use for next greater/smaller, or histogram rectangle.
// Next greater element (for each index)
vector<int> next_greater(const vector<int>& a) {
int n = a.size();
vector<int> ng(n, -1);
vector<int> st;
for (int i = 0; i < n; i++) {
while (!st.empty() && a[st.back()] < a[i]) {
ng[st.back()] = a[i];
st.pop_back();
}
st.push_back(i);
}
return ng;
}
// Circular: wrap with 2*n and only push when i < n
vector<int> next_greater_circular(const vector<int>& a) {
int n = a.size();
vector<int> ng(n, -1);
vector<int> st;
for (int i = 0; i < 2 * n; i++) {
int j = i % n;
while (!st.empty() && a[st.back()] < a[j]) {
ng[st.back()] = a[j];
st.pop_back();
}
if (i < n) st.push_back(j);
}
return ng;
}
| ID | Title | Link |
|---|---|---|
| 739 | Daily Temperatures | Link |
| 42 | Trapping Rain Water | Link |
| 84 | Largest Rectangle in Histogram | Link |
| 503 | Next Greater Element II | Link |
| 1944 | Visible People in Queue | Link |
Monotonic Queue
When to use: You need the maximum or minimum within a sliding window of fixed size, or need to maintain a monotonic property as elements enter and leave a window.
Deque of indices with values in monotonic order. Sliding window max/min.
// Sliding window maximum (window size k)
vector<int> max_sliding_window(const vector<int>& a, int k) {
deque<int> dq;
vector<int> out;
for (int i = 0; i < (int)a.size(); i++) {
while (!dq.empty() && a[dq.back()] <= a[i]) dq.pop_back();
dq.push_back(i);
if (dq.front() <= i - k) dq.pop_front();
if (i >= k - 1) out.push_back(a[dq.front()]);
}
return out;
}
| ID | Title | Link |
|---|---|---|
| 239 | Sliding Window Maximum | Link |
| 1438 | Longest Continuous Subarray With Abs Diff ≤ Limit | Link |
Heap / Priority Queue
When to use: You repeatedly need the smallest (or largest) element — merging k sorted lists, scheduling, median maintenance, or any “top K” problem.
Min-heap: priority_queue<T, vector<T>, greater<T>>. K-way merge: push heads, pop min, push next from same list.
// K-way merge of sorted arrays (or list heads)
vector<int> merge_k_sorted(const vector<vector<int>>& lists) {
using T = tuple<int, int, int>;
priority_queue<T, vector<T>, greater<T>> pq;
for (int i = 0; i < (int)lists.size(); i++)
if (!lists[i].empty()) pq.emplace(lists[i][0], i, 0);
vector<int> out;
while (!pq.empty()) {
auto [v, i, j] = pq.top();
pq.pop();
out.push_back(v);
if (j + 1 < (int)lists[i].size()) pq.emplace(lists[i][j + 1], i, j + 1);
}
return out;
}
| ID | Title | Link |
|---|---|---|
| 23 | Merge k Sorted Lists | Link |
| 295 | Find Median from Data Stream | Link |
Union-Find (DSU)
When to use: You need to track connected components, determine if two nodes are in the same group, or merge groups — common in graph connectivity, redundant edge detection, and “accounts merge” problems.
Path compression + rank merge. find(x), unite(a,b).
struct DSU {
vector<int> 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] < r[b]) swap(a, b);
p[b] = a;
if (r[a] == r[b]) r[a]++;
return true;
}
};
| ID | Title | Link |
|---|---|---|
| 684 | Redundant Connection | Link |
| 721 | Accounts Merge | Link |
| 1319 | Number of Operations to Make Network Connected | Link |
Trie
When to use: Problems involve prefix matching, autocomplete, word search in a dictionary, or “find all words with prefix X”. Also useful for XOR-maximization with a bitwise trie.
| Fixed alphabet (e.g. 26). Insert and search in O( | s | ). |
struct Trie {
struct Node {
int nxt[26];
bool end = false;
Node() { memset(nxt, -1, sizeof nxt); }
};
vector<Node> t{1};
void insert(const string& s) {
int u = 0;
for (char c : s) {
int i = c - 'a';
if (t[u].nxt[i] == -1) { t[u].nxt[i] = t.size(); t.emplace_back(); }
u = t[u].nxt[i];
}
t[u].end = true;
}
bool search(const string& s) {
int u = 0;
for (char c : s) {
u = t[u].nxt[c - 'a'];
if (u == -1) return false;
}
return t[u].end;
}
};
| ID | Title | Link |
|---|---|---|
| 208 | Implement Trie | Link |
| 211 | Design Add and Search Words | Link |
| 212 | Word Search II | Link |
Segment Tree
When to use: You need both range queries (sum, min, max) AND point or range updates on the same array. More powerful than Fenwick tree when you need lazy propagation or non-commutative operations.
0-indexed range [0, n-1]. Point update, range sum (or min/max). Recursive implementation.
struct SegTree {
int n;
vector<long long> st;
SegTree(int n) : n(n), st(4 * n, 0) {}
void upd(int i, int l, int r, int p, long long v) {
if (l == r) { st[i] = v; return; }
int m = (l + r) / 2;
if (p <= m) upd(2 * i, l, m, p, v);
else upd(2 * i + 1, m + 1, r, p, v);
st[i] = st[2 * i] + st[2 * i + 1];
}
long long qry(int i, int l, int r, int ql, int qr) {
if (qr < l || r < ql) return 0;
if (ql <= l && r <= qr) return st[i];
int m = (l + r) / 2;
return qry(2 * i, l, m, ql, qr) + qry(2 * i + 1, m + 1, r, ql, qr);
}
void upd(int p, long long v) { upd(1, 0, n - 1, p, v); }
long long qry(int ql, int qr) { return qry(1, 0, n - 1, ql, qr); }
};
| ID | Title | Link |
|---|---|---|
| 307 | Range Sum Query – Mutable | Link |
| 732 | My Calendar III | Link |
Fenwick Tree (BIT)
When to use: You need prefix sums with point updates — simpler and faster constant than segment tree when you don’t need lazy propagation. Great for counting inversions or “count of smaller numbers after self”.
1-indexed. Point add, prefix sum. Range sum [l, r] = sum(r) - sum(l-1).
struct BIT {
int n;
vector<long long> f;
BIT(int n) : n(n), f(n + 1, 0) {}
void add(int i, long long v) {
for (; i <= n; i += i & -i) f[i] += v;
}
long long sum(int i) {
long long s = 0;
for (; i > 0; i -= i & -i) s += f[i];
return s;
}
long long range_sum(int l, int r) { return sum(r) - sum(l - 1); }
};
| ID | Title | Link |
|---|---|---|
| 307 | Range Sum Query – Mutable | Link |
| 315 | Count of Smaller Numbers After Self | Link |
| 308 | Range Sum Query 2D – Mutable | Link |
Sparse Table (Range Min/Max)
When to use: You need O(1) range min/max/gcd queries with NO updates. Perfect for static arrays where you precompute once and query many times.
O(n log n) build, O(1) range min/max. Idempotent only (min, max, gcd). 0-indexed.
struct SparseTable {
vector<vector<int>> st;
vector<int> lg;
int op(int a, int b) { return min(a, b); } // or max
SparseTable(const vector<int>& a) {
int n = a.size();
lg.assign(n + 1, 0);
for (int i = 2; i <= n; i++) lg[i] = lg[i / 2] + 1;
int k = lg[n] + 1;
st.assign(n, vector<int>(k));
for (int i = 0; i < n; i++) st[i][0] = a[i];
for (int j = 1; j < k; j++)
for (int i = 0; i + (1 << j) <= n; i++)
st[i][j] = op(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
}
int qry(int l, int r) {
int j = lg[r - l + 1];
return op(st[l][j], st[r - (1 << j) + 1][j]);
}
};
| ID | Title | Link |
|---|---|---|
| — | Range min/max, GCD (no update) | — |
Quick Reference
| Structure | When to Use | Operations | Time |
|---|---|---|---|
| Binary Search | Sorted data, find boundary | lower/upper bound | O(log n) |
| Prefix Sum | Range sum queries | build + query | O(n) + O(1) |
| Monotonic Stack | Next greater/smaller | push/pop | O(n) |
| DSU | Connected components, union | find/union | O(α(n)) |
| Trie | Prefix search, autocomplete | insert/search | O(L) |
| Segment Tree | Range query + update | build/query/update | O(n) + O(log n) |
| Fenwick Tree | Prefix sums + point update | update/query | O(log n) |
More Templates
- Beginner’s Guide: LeetCode Beginner’s Guide
- Graph (BFS, Dijkstra, Topo, DSU): Graph Templates
- Binary search (rotated, 2D, answer space): Search Templates
- DP, Backtracking, Greedy, Stack: Categories & Templates