Appendix A: Complete STL Guide
The C++ Standard Template Library (STL) is the competitive programmer’s and interview candidate’s most powerful weapon. This appendix covers every major container, algorithm, and utility you need.
1. Sequence Containers
1.1 std::vector
The workhorse of competitive programming. Dynamic array with amortized O(1) push_back.
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
int main() {
// Construction
vector<int> v; // empty
vector<int> v2(10); // 10 elements, value 0
vector<int> v3(10, 42); // 10 elements, value 42
vector<int> v4 = {1, 2, 3}; // initializer list
vector<int> v5(v4.begin(), v4.end()); // from iterator range
// Adding elements
v.push_back(1); // O(1) amortized
v.emplace_back(2); // constructs in-place, slightly faster
v.insert(v.begin(), 0); // O(n) - shifts elements
v.insert(v.begin() + 2, 5); // insert at position 2
// Accessing
v[0]; // no bounds check
v.at(0); // throws std::out_of_range
v.front(); // first element
v.back(); // last element
v.data(); // pointer to underlying array
// Size and capacity
v.size(); // number of elements
v.empty(); // true if size == 0
v.capacity(); // allocated storage
v.reserve(100); // pre-allocate (doesn't change size)
v.resize(20); // change size (adds/removes elements)
v.shrink_to_fit(); // request to free unused memory
// Removing
v.pop_back(); // O(1) - remove last
v.erase(v.begin()); // O(n) - remove first
v.erase(v.begin(), v.begin() + 3); // erase range
v.clear(); // remove all, doesn't free memory
// Remove-erase idiom (CRITICAL for interviews)
v = {1, 2, 3, 2, 5, 2};
v.erase(remove(v.begin(), v.end(), 2), v.end()); // removes all 2s
// Iterators
for (auto it = v.begin(); it != v.end(); ++it) { /* ... */ }
for (auto it = v.rbegin(); it != v.rend(); ++it) { /* reverse */ }
for (int x : v) { /* range-based for */ }
// Comparison (lexicographic)
vector<int> a = {1, 2, 3}, b = {1, 2, 4};
bool less = (a < b); // true
// 2D vectors
vector<vector<int>> grid(3, vector<int>(4, 0)); // 3x4 grid of zeros
}
Key facts:
- Memory layout: contiguous (cache-friendly, pointer arithmetic works)
- Iterator invalidation:
push_backmay invalidate all iterators if reallocation occurs emplace_backvspush_back:emplace_backconstructs in-place, avoids copy/move
1.2 std::deque
Double-ended queue. O(1) push/pop at both ends.
#include <deque>
int main() {
deque<int> dq;
dq.push_back(1); // O(1)
dq.push_front(0); // O(1) - vector can't do this!
dq.pop_back(); // O(1)
dq.pop_front(); // O(1)
dq[0]; // O(1) random access
dq.size();
// Useful for: sliding window, monotonic deque
}
Key facts:
- NOT contiguous memory (no
.data(), pointer arithmetic doesn’t work) - Random access is O(1) but slower than vector
- Use when you need push_front/pop_front
1.3 std::list
Doubly-linked list.
#include <list>
int main() {
list<int> lst = {1, 2, 3, 4, 5};
lst.push_back(6); // O(1)
lst.push_front(0); // O(1)
lst.pop_back(); // O(1)
lst.pop_front(); // O(1)
// Insert/erase without invalidating other iterators
auto it = lst.begin();
advance(it, 2); // O(n) - no random access
lst.insert(it, 99); // O(1)
lst.erase(it); // O(1)
// Splice - move elements between lists in O(1)
list<int> other = {10, 20};
lst.splice(lst.begin(), other); // move all of 'other' to front
// Sort (specialized, not std::sort)
lst.sort(); // O(n log n), stable
lst.unique(); // remove consecutive duplicates (sort first)
lst.reverse(); // O(n)
lst.merge(other); // merge sorted lists
// NO random access - lst[0] doesn't compile
}
When to use: Almost never in competitive programming. Useful when you need stable iterators during insert/erase.
1.4 std::forward_list
Singly-linked list. More memory-efficient than list.
#include <forward_list>
int main() {
forward_list<int> fl = {1, 2, 3};
fl.push_front(0); // O(1)
fl.pop_front(); // O(1)
// No push_back, no size(), no back()
auto it = fl.before_begin(); // iterator before first element
fl.insert_after(it, 99); // O(1)
fl.erase_after(it); // O(1)
fl.sort(); // O(n log n)
}
When to use: Memory-constrained scenarios. Rarely needed.
1.5 std::array
Fixed-size array. Stack-allocated, zero overhead.
#include <array>
int main() {
array<int, 5> arr = {1, 2, 3, 4, 5};
arr[0]; // O(1)
arr.at(0); // bounds-checked
arr.size(); // always 5
arr.front();
arr.back();
arr.fill(0); // set all to 0
// Works with STL algorithms
sort(arr.begin(), arr.end());
// Can be returned from functions (unlike C arrays)
}
2. Associative Containers
All associative containers are ordered (by default, using <). They use balanced BSTs (typically red-black trees).
2.1 std::set
Sorted set of unique elements.
#include <set>
int main() {
set<int> s = {3, 1, 4, 1, 5}; // {1, 3, 4, 5}
// Insertion
s.insert(2); // O(log n)
s.emplace(6); // O(log n)
auto [it, inserted] = s.insert(3); // inserted = false (already exists)
// Lookup
s.count(3); // 0 or 1 (O(log n))
s.find(3); // iterator or s.end() (O(log n))
s.contains(3); // C++20
// Deletion
s.erase(3); // O(log n)
s.erase(s.begin()); // O(1) amortized
s.erase(s.begin(), s.find(4)); // range erase
// Bounds (CRITICAL for interviews)
s = {1, 3, 5, 7, 9};
auto lo = s.lower_bound(4); // iterator to 5 (first >= 4)
auto hi = s.upper_bound(4); // iterator to 5 (first > 4)
auto lo2 = s.lower_bound(5); // iterator to 5 (first >= 5)
auto hi2 = s.upper_bound(5); // iterator to 7 (first > 5)
// For range [lo, hi]: use lower_bound(lo) and upper_bound(hi)
// This gives all elements in [lo, hi]
// Iteration (sorted order)
for (int x : s) { /* 1, 3, 5, 7, 9 */ }
// Custom comparator
set<int, greater<int>> desc; // descending order
}
2.2 std::multiset
Like set but allows duplicates.
#include <set>
int main() {
multiset<int> ms = {1, 1, 2, 2, 3};
ms.insert(2); // now has three 2's
ms.count(2); // 3
// Erase ALL occurrences
ms.erase(2); // removes all 2's, returns count
// Erase ONE occurrence
ms.erase(ms.find(2)); // removes just one
// Equal range: all elements with value x
auto [lo, hi] = ms.equal_range(2);
int count = distance(lo, hi);
// Lower/upper bound work as expected
// lower_bound(2) -> first 2
// upper_bound(2) -> first element after all 2's
}
2.3 std::map
Sorted key-value pairs.
#include <map>
#include <string>
int main() {
map<string, int> mp;
// Insertion
mp["alice"] = 95; // O(log n), creates if not exists
mp.insert({"bob", 87}); // O(log n)
mp.emplace("carol", 92); // O(log n)
// Access (BE CAREFUL: operator[] creates default if missing!)
int score = mp["alice"]; // 95
int zero = mp["dave"]; // creates "dave" -> 0 (BAD in read-only context!)
// Safe access
auto it = mp.find("dave");
if (it != mp.end()) { /* it->second */ }
// C++20
if (mp.contains("alice")) { /* ... */ }
// Iteration (sorted by key)
for (auto& [key, value] : mp) {
// structured binding (C++17)
}
// Bounds
auto lo = mp.lower_bound("bob"); // first key >= "bob"
auto hi = mp.upper_bound("carol"); // first key > "carol"
// Merge with [] for frequency counting
vector<int> nums = {1, 2, 3, 2, 1};
map<int, int> freq;
for (int x : nums) freq[x]++;
// freq: {1:2, 2:2, 3:1}
}
2.4 std::multimap
Like map but allows duplicate keys.
#include <map>
int main() {
multimap<string, int> grades;
grades.insert({"alice", 95});
grades.insert({"alice", 87}); // alice has two grades
grades.insert({"bob", 92});
// No operator[] for multimap!
grades.find("alice"); // iterator to first "alice"
auto [lo, hi] = grades.equal_range("alice");
for (auto it = lo; it != hi; ++it) {
// it->second: 95, 87
}
}
3. Unordered Containers
Hash-based. O(1) average, O(n) worst case.
3.1 std::unordered_set
#include <unordered_set>
int main() {
unordered_set<int> us;
us.insert(1); // O(1) average
us.count(1); // 0 or 1, O(1) average
us.find(1); // iterator, O(1) average
us.erase(1); // O(1) average
// Custom hash (for pairs, custom types)
struct PairHash {
size_t operator()(const pair<int,int>& p) const {
return hash<long long>()(((long long)p.first << 32) | p.second);
}
};
unordered_set<pair<int,int>, PairHash> ps;
// Bucket interface
us.bucket_count(); // number of buckets
us.load_factor(); // size / bucket_count
us.max_load_factor(); // default 1.0
}
3.2 std::unordered_map
#include <unordered_map>
#include <string>
int main() {
unordered_map<string, int> um;
um["key"] = 42; // O(1) average
um.find("key"); // O(1) average
um.count("key"); // O(1) average
um.erase("key"); // O(1) average
// Same caution with operator[] as map
}
When to use unordered vs ordered:
- Unordered: when you only need insert/find/erase, don’t need ordering
- Ordered: when you need iteration in sorted order, or need lower_bound/upper_bound
- In practice, unordered containers are faster for large datasets but have higher constant factor for small n
4. Container Adaptors
4.1 std::stack
LIFO. Default uses deque.
#include <stack>
int main() {
stack<int> st;
st.push(1); // O(1)
st.emplace(2); // O(1)
st.top(); // O(1) - peek
st.pop(); // O(1) - no return value!
st.empty(); // O(1)
st.size(); // O(1)
// Can use vector as underlying container
stack<int, vector<int>> st2;
}
4.2 std::queue
FIFO. Default uses deque.
#include <queue>
int main() {
queue<int> q;
q.push(1); // O(1)
q.emplace(2); // O(1)
q.front(); // O(1) - peek front
q.back(); // O(1) - peek back
q.pop(); // O(1) - no return value!
q.empty();
q.size();
}
4.3 std::priority_queue
Max-heap by default.
#include <queue>
#include <vector>
#include <functional>
int main() {
// Max-heap (default)
priority_queue<int> maxpq;
maxpq.push(3); // O(log n)
maxpq.push(1);
maxpq.push(4);
maxpq.top(); // 4 (O(1))
maxpq.pop(); // O(log n)
// Min-heap
priority_queue<int, vector<int>, greater<int>> minpq;
minpq.push(3);
minpq.push(1);
minpq.top(); // 1
// Custom comparator (pair-based)
using P = pair<int, int>;
priority_queue<P, vector<P>, greater<P>> pq; // min-heap by first
// Build from existing container: O(n)
vector<int> v = {3, 1, 4, 1, 5};
priority_queue<int> pq2(v.begin(), v.end());
// Custom comparator with lambda (C++17)
auto cmp = [](int a, int b) { return a > b; }; // min-heap
priority_queue<int, vector<int>, decltype(cmp)> pq3(cmp);
}
5. Iterators
5.1 Iterator Categories
| Category | Capabilities | Containers |
|---|---|---|
| Input | Read once, forward | istream_iterator |
| Output | Write once, forward | ostream_iterator |
| Forward | Read/write, forward, multi-pass | forward_list, unordered_* |
| Bidirectional | + backward | list, set, map |
| Random Access | + arithmetic (+n, -n, []) | vector, deque, array |
| Contiguous | + contiguous memory | vector, array, string (C++20) |
5.2 Iterator Operations
#include <iterator>
#include <vector>
#include <iostream>
int main() {
vector<int> v = {1, 2, 3, 4, 5};
// Basic operations
auto it = v.begin();
*it; // dereference: 1
++it; // advance: now points to 2
--it; // back: now points to 1
it + 3; // random access: points to 4
it[2]; // equivalent to *(it + 2): 3
distance(v.begin(), v.end()); // 5
// Advance (modifies iterator)
auto it2 = v.begin();
advance(it2, 3); // it2 now points to 4
// Next/Prev (returns new iterator)
auto it3 = next(v.begin()); // points to 2
auto it4 = prev(v.end()); // points to 5
auto it5 = next(v.begin(), 3); // points to 4
// Insert iterators
vector<int> dest;
back_insert_iterator<vector<int>> bii(dest);
*bii = 10; // same as dest.push_back(10)
// Or more commonly:
copy(v.begin(), v.end(), back_inserter(dest));
// Stream iterators
copy(v.begin(), v.end(), ostream_iterator<int>(cout, " "));
// Output: 1 2 3 4 5
}
5.3 Reverse Iterators
vector<int> v = {1, 2, 3, 4, 5};
// Reverse iteration
for (auto it = v.rbegin(); it != v.rend(); ++it) {
// 5, 4, 3, 2, 1
}
// Convert reverse to forward
auto rit = v.rbegin(); // points to 5
auto fit = rit.base(); // points to v.end()
// CRITICAL: reverse_iterator base() is off by one!
// rit points to element X, rit.base() points to element AFTER X
6. Algorithms
#include <algorithm>
#include <numeric>
#include <functional>
6.1 Sorting
vector<int> v = {3, 1, 4, 1, 5, 9};
// Basic sort: O(n log n)
sort(v.begin(), v.end()); // ascending
sort(v.begin(), v.end(), greater<>()); // descending
// Custom comparator
sort(v.begin(), v.end(), [](int a, int b) {
return a > b; // descending
});
// Sorting pairs/tuples: compares lexicographically
vector<pair<int,int>> vp = {{1,3}, {1,2}, {2,1}};
sort(vp.begin(), vp.end());
// Result: {{1,2}, {1,3}, {2,1}}
// Stable sort: preserves relative order of equal elements
stable_sort(v.begin(), v.end());
// Partial sort: only first k elements sorted
partial_sort(v.begin(), v.begin() + 3, v.end());
// First 3 elements are the 3 smallest, sorted
// nth_element: partition around nth position
nth_element(v.begin(), v.begin() + 3, v.end());
// v[3] is what would be there if fully sorted
// All elements before are <= v[3], all after are >= v[3]
// O(n) average!
// Is sorted?
is_sorted(v.begin(), v.end());
6.2 Binary Search
vector<int> v = {1, 2, 3, 4, 5, 5, 5, 6, 7};
// Binary search: does element exist? O(log n)
binary_search(v.begin(), v.end(), 5); // true
// Lower bound: first element >= value
auto lo = lower_bound(v.begin(), v.end(), 5); // points to first 5
int idx = lo - v.begin(); // index: 4
// Upper bound: first element > value
auto hi = upper_bound(v.begin(), v.end(), 5); // points to 6
int idx2 = hi - v.begin(); // index: 7
// Count of 5s in sorted range:
int count = upper_bound(v.begin(), v.end(), 5) -
lower_bound(v.begin(), v.end(), 5); // 3
// Equal range: returns pair of (lower_bound, upper_bound)
auto [lo2, hi2] = equal_range(v.begin(), v.end(), 5);
// IMPORTANT: these require SORTED data
// For unsorted data, use find() which is O(n)
6.3 Permutations
vector<int> v = {1, 2, 3};
// Next permutation: O(n)
do {
// process permutation
} while (next_permutation(v.begin(), v.end()));
// Generates all permutations in lexicographic order
// START with sorted input to get ALL permutations
// Previous permutation
prev_permutation(v.begin(), v.end());
// To check if it's the last permutation:
// next_permutation returns false when it wraps around
6.4 Unique and Remove
vector<int> v = {1, 1, 2, 2, 2, 3, 3};
// Unique: removes CONSECUTIVE duplicates (data must be sorted)
auto new_end = unique(v.begin(), v.end());
// v is now: {1, 2, 3, ?, ?, ?, ?} (new_end points past 3)
// Proper way to get unique elements:
v.erase(unique(v.begin(), v.end()), v.end());
// v is now: {1, 2, 3}
// Remove: moves elements matching value to end
v = {1, 2, 3, 2, 5, 2};
auto new_end2 = remove(v.begin(), v.end(), 2);
v.erase(new_end2, v.end()); // remove-erase idiom
// v is now: {1, 3, 5}
// Remove_if: remove with predicate
v = {1, 2, 3, 4, 5, 6};
v.erase(remove_if(v.begin(), v.end(), [](int x) { return x % 2 == 0; }), v.end());
// v is now: {1, 3, 5}
6.5 Rotate and Reverse
vector<int> v = {1, 2, 3, 4, 5};
// Rotate: makes element at middle the first element
rotate(v.begin(), v.begin() + 2, v.end());
// v is now: {3, 4, 5, 1, 2}
// Reverse
reverse(v.begin(), v.end());
6.6 Accumulate and Prefix Sums
#include <numeric>
vector<int> v = {1, 2, 3, 4, 5};
// Sum
int sum = accumulate(v.begin(), v.end(), 0); // 15
// Product
int prod = accumulate(v.begin(), v.end(), 1, multiplies<int>()); // 120
// String concatenation
vector<string> words = {"hello", " ", "world"};
string s = accumulate(words.begin(), words.end(), string(""));
// Partial sum: prefix sums
vector<int> prefix(v.size());
partial_sum(v.begin(), v.end(), prefix.begin());
// prefix: {1, 3, 6, 10, 15}
// Adjacent difference
vector<int> diff(v.size());
adjacent_difference(v.begin(), v.end(), diff.begin());
// diff: {1, 1, 1, 1, 1}
6.7 Min/Max and Clamping
vector<int> v = {3, 1, 4, 1, 5, 9};
// Min and max element
auto [mn, mx] = minmax_element(v.begin(), v.end());
// *mn = 1, *mx = 9
// Min and max of values
min(3, 5); // 3
max(3, 5); // 5
min({1, 2, 3, 4}); // 1 (initializer list)
max({1, 2, 3, 4}); // 4
// Clamp (C++17)
int x = clamp(15, 0, 10); // 10 (clamped to max)
int y = clamp(-5, 0, 10); // 0 (clamped to min)
int z = clamp(5, 0, 10); // 5 (within range)
6.8 Count and Find
vector<int> v = {1, 2, 3, 2, 5};
// Count
int cnt = count(v.begin(), v.end(), 2); // 2
int cnt2 = count_if(v.begin(), v.end(),
[](int x) { return x > 2; }); // 2
// Find
auto it = find(v.begin(), v.end(), 3); // iterator to 3
auto it2 = find_if(v.begin(), v.end(),
[](int x) { return x > 3; }); // iterator to 5
auto it3 = find_if_not(v.begin(), v.end(),
[](int x) { return x < 3; }); // iterator to 3
// All/any/none (C++11)
all_of(v.begin(), v.end(), [](int x) { return x > 0; }); // true
any_of(v.begin(), v.end(), [](int x) { return x > 4; }); // true
none_of(v.begin(), v.end(), [](int x) { return x < 0; }); // true
6.9 Merge and Set Operations
vector<int> a = {1, 3, 5}, b = {2, 4, 6};
// Merge (both must be sorted)
vector<int> merged(a.size() + b.size());
merge(a.begin(), a.end(), b.begin(), b.end(), merged.begin());
// merged: {1, 2, 3, 4, 5, 6}
// Inplace merge
vector<int> c = {1, 3, 5, 2, 4, 6};
inplace_merge(c.begin(), c.begin() + 3, c.end());
// c: {1, 2, 3, 4, 5, 6}
// Set operations (all require sorted ranges)
set_union(a.begin(), a.end(), b.begin(), b.end(), back_inserter(result));
set_intersection(a.begin(), a.end(), b.begin(), b.end(), back_inserter(result));
set_difference(a.begin(), a.end(), b.begin(), b.end(), back_inserter(result));
set_symmetric_difference(a.begin(), a.end(), b.begin(), b.end(), back_inserter(result));
// Includes: is one range a subset of another?
includes(a.begin(), a.end(), a.begin(), a.begin() + 2); // true
6.10 Heap Operations
vector<int> v = {3, 1, 4, 1, 5, 9};
// Make heap: O(n)
make_heap(v.begin(), v.end()); // max-heap: v[0] = 9
// Push: add element then heapify
v.push_back(7);
push_heap(v.begin(), v.end()); // O(log n)
// Pop: move top to end, then heapify
pop_heap(v.begin(), v.end()); // v.back() is now the max
int max_val = v.back();
v.pop_back();
// Sort heap: O(n log n)
sort_heap(v.begin(), v.end());
// Check
is_heap(v.begin(), v.end());
6.11 Partitioning
vector<int> v = {1, 2, 3, 4, 5, 6};
// Partition: elements satisfying predicate come first
auto it = partition(v.begin(), v.end(),
[](int x) { return x % 2 == 0; });
// Possible result: {6, 2, 4, 3, 5, 1} (relative order may change)
// Stable partition: preserves relative order
auto it2 = stable_partition(v.begin(), v.end(),
[](int x) { return x % 2 == 0; });
// Result: {2, 4, 6, 1, 3, 5}
// Partition point (C++11)
auto pp = partition_point(v.begin(), v.end(),
[](int x) { return x % 2 == 0; });
6.12 Copy and Transform
vector<int> src = {1, 2, 3, 4, 5};
vector<int> dst(5);
// Copy
copy(src.begin(), src.end(), dst.begin());
copy_if(src.begin(), src.end(), back_inserter(dst),
[](int x) { return x > 3; });
copy_n(src.begin(), 3, dst.begin()); // copy first 3
copy_backward(src.begin(), src.end(), dst.end()); // copy in reverse
// Transform
vector<int> result(5);
transform(src.begin(), src.end(), result.begin(),
[](int x) { return x * 2; });
// Binary transform
vector<int> a = {1, 2, 3}, b = {4, 5, 6}, c(3);
transform(a.begin(), a.end(), b.begin(), c.begin(),
[](int x, int y) { return x + y; });
// c: {5, 7, 9}
// Fill and generate
fill(v.begin(), v.end(), 0);
fill_n(v.begin(), 5, 42); // fill first 5 with 42
generate(v.begin(), v.end(), [n = 0]() mutable { return n++; });
iota(v.begin(), v.end(), 1); // 1, 2, 3, 4, 5
6.13 Mismatch and Equal
vector<int> a = {1, 2, 3, 4, 5};
vector<int> b = {1, 2, 3, 5, 6};
// Mismatch: find first difference
auto [ia, ib] = mismatch(a.begin(), a.end(), b.begin());
// *ia = 4, *ib = 5
// Equal
equal(a.begin(), a.begin() + 3, b.begin()); // true
equal(a.begin(), a.end(), b.begin(), b.end()); // false
// Lexicographical compare
lexicographical_compare(a.begin(), a.end(), b.begin(), b.end());
7. Functors and Lambda Expressions
7.1 Built-in Functors
#include <functional>
plus<int>()(3, 4); // 7
minus<int>()(3, 4); // -1
multiplies<int>()(3, 4); // 12
divides<int>()(10, 3); // 3
modulus<int>()(10, 3); // 1
negate<int>()(5); // -5
equal_to<int>()(3, 3); // true
not_equal_to<int>()(3, 4); // true
greater<int>()(3, 4); // false
less<int>()(3, 4); // true
greater_equal<int>()(3, 3); // true
less_equal<int>()(3, 4); // true
logical_and<int>()(1, 1); // true
logical_or<int>()(0, 1); // true
logical_not<int>()(0); // true
7.2 Lambda Expressions
// Basic syntax: [capture](params) -> return_type { body }
// Simple lambda
auto add = [](int a, int b) { return a + b; };
add(3, 4); // 7
// Capture by value
int x = 10;
auto f1 = [x]() { return x; }; // x captured by value
// Capture by reference
auto f2 = [&x]() { x++; }; // x captured by reference
// Capture all
auto f3 = [=]() { return x; }; // all by value
auto f4 = [&]() { x++; }; // all by reference
// Mutable lambda (can modify captured-by-value)
auto f5 = [x]() mutable { x++; return x; };
// Generic lambda (C++14)
auto f6 = [](auto a, auto b) { return a + b; };
// Init capture (C++14)
auto f7 = [y = x + 1]() { return y; };
// Immediately invoked lambda (IIFE)
int result = [](int n) {
return n * n;
}(5); // result = 25
// Lambda in STL
vector<int> v = {3, 1, 4, 1, 5};
sort(v.begin(), v.end(), [](int a, int b) {
return a > b; // descending
});
// Lambda with structured binding (C++17)
vector<pair<int,int>> vp = {{1,3}, {2,1}, {3,2}};
sort(vp.begin(), vp.end(), [](const auto& a, const auto& b) {
return a.second < b.second;
});
7.3 std::function
#include <functional>
// Type-erased callable
function<int(int, int)> op;
op = [](int a, int b) { return a + b; };
op(3, 4); // 7
// Can hold function pointers, lambdas, functors
int (*fp)(int, int) = [](int a, int b) { return a * b; };
op = fp;
// Recursive lambda with function
function<int(int)> fib = [&](int n) -> int {
return n <= 1 ? n : fib(n-1) + fib(n-2);
};
8. pair and tuple
8.1 std::pair
#include <utility>
pair<int, string> p = {42, "hello"};
p.first; // 42
p.second; // "hello"
// Make pair
auto p2 = make_pair(1, "world");
// Comparison (lexicographic: compares first, then second)
pair<int,int> a = {1, 2}, b = {1, 3};
bool less = (a < b); // true
// Structured binding (C++17)
auto [x, y] = p; // x = 42, y = "hello"
// Swap
swap(p, p2);
// Useful for returning two values
pair<int, int> minMax(vector<int>& v) {
return {*min_element(v.begin(), v.end()),
*max_element(v.begin(), v.end())};
}
8.2 std::tuple
#include <tuple>
tuple<int, string, double> t = {42, "hello", 3.14};
// Access
get<0>(t); // 42
get<1>(t); // "hello"
get<double>(t); // 3.14 (by type, if unique)
// Structured binding (C++17)
auto [a, b, c] = t;
// Make tuple
auto t2 = make_tuple(1, "world", 2.71);
// Tie: create tuple of references
int x; string y; double z;
tie(x, y, z) = t; // assigns values
// Tie with ignore
tie(ignore, y, ignore) = t; // only assign second
// Tuple comparison (lexicographic)
tuple<int,int,int> t1 = {1, 2, 3};
tuple<int,int,int> t2 = {1, 2, 4};
bool less = (t1 < t2); // true
// Apply: call function with tuple as args
auto sum = [](int a, string b, double c) { return a + c; };
double result = apply(sum, t);
// Tuple size
tuple_size<decltype(t)>::value; // 3
9. Common Patterns and Idioms
9.1 Frequency Count
// With map
map<int, int> freq;
for (int x : arr) freq[x]++;
// With unordered_map (faster)
unordered_map<int, int> freq2;
for (int x : arr) freq2[x]++;
// With vector (when values are small, e.g., 0-255)
vector<int> freq3(256);
for (int x : arr) freq3[x]++;
9.2 Coordinate Compression
void compress(vector<int>& coords) {
vector<int> sorted = coords;
sort(sorted.begin(), sorted.end());
sorted.erase(unique(sorted.begin(), sorted.end()), sorted.end());
for (int& x : coords) {
x = lower_bound(sorted.begin(), sorted.end(), x) - sorted.begin();
}
}
9.3 Merge Intervals
vector<pair<int,int>> mergeIntervals(vector<pair<int,int>>& intervals) {
sort(intervals.begin(), intervals.end());
vector<pair<int,int>> merged;
for (auto& [l, r] : intervals) {
if (!merged.empty() && l <= merged.back().second) {
merged.back().second = max(merged.back().second, r);
} else {
merged.push_back({l, r});
}
}
return merged;
}
9.4 Sliding Window with Deque
// Max in sliding window of size k
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
deque<int> dq; // stores indices
vector<int> result;
for (int i = 0; i < nums.size(); i++) {
while (!dq.empty() && dq.front() <= i - k)
dq.pop_front();
while (!dq.empty() && nums[dq.back()] <= nums[i])
dq.pop_back();
dq.push_back(i);
if (i >= k - 1)
result.push_back(nums[dq.front()]);
}
return result;
}
9.5 Custom Hash for Unordered Containers
struct custom_hash {
static uint64_t splitmix64(uint64_t x) {
x += 0x9e3779b97f4a7c15;
x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9;
x = (x ^ (x >> 27)) * 0x94d049bb133111eb;
return x ^ (x >> 31);
}
size_t operator()(uint64_t x) const {
static const uint64_t FIXED_RANDOM =
chrono::steady_clock::now().time_since_epoch().count();
return splitmix64(x + FIXED_RANDOM);
}
size_t operator()(const pair<int,int>& p) const {
return operator()(((uint64_t)p.first << 32) | p.second);
}
};
unordered_map<pair<int,int>, int, custom_hash> mp;
9.6 Multiset for Running Median / Order Statistics
#include <set>
multiset<int> lo, hi; // lo: lower half, hi: upper half
void add(int x) {
lo.insert(x);
hi.insert(*lo.rbegin());
lo.erase(prev(lo.end()));
if (hi.size() > lo.size()) {
lo.insert(*hi.begin());
hi.erase(hi.begin());
}
}
int getMedian() {
return *lo.rbegin();
}
9.7 Using lower_bound / upper_bound Correctly
// For set/map: use member functions (O(log n) guaranteed)
set<int> s = {1, 3, 5, 7};
auto it = s.lower_bound(4); // correct: O(log n)
// DON'T use std::lower_bound on set (O(n) for non-random-access!)
auto it2 = lower_bound(s.begin(), s.end(), 4); // BAD: O(n)
// For vector: use std::lower_bound (O(log n), requires sorted)
vector<int> v = {1, 3, 5, 7};
auto it3 = lower_bound(v.begin(), v.end(), 4); // correct: O(log n)
9.8 Structured Bindings (C++17)
// Map iteration
map<string, int> mp = {{"a", 1}, {"b", 2}};
for (auto& [key, value] : mp) {
cout << key << ": " << value << endl;
}
// Pair/tuple
auto [x, y] = make_pair(1, 2);
auto [a, b, c] = make_tuple(1, 2, 3);
// Array
int arr[] = {1, 2, 3};
auto [p, q, r] = arr;
9.9 std::optional (C++17)
#include <optional>
optional<int> findIndex(vector<int>& v, int target) {
for (int i = 0; i < v.size(); i++)
if (v[i] == target) return i;
return nullopt;
}
auto idx = findIndex(v, 42);
if (idx.has_value()) {
cout << "Found at: " << idx.value() << endl;
}
// Or simply:
if (idx) cout << *idx << endl;
9.10 std::variant (C++17)
#include <variant>
variant<int, string> v = 42;
cout << get<int>(v); // 42
v = "hello";
cout << get<string>(v); // "hello"
// Visitor pattern
visit([](auto& arg) {
cout << arg;
}, v);
9.11 std::gcd and std::lcm (C++17)
#include <numeric>
gcd(12, 8); // 4
lcm(12, 8); // 24
9.12 String-Number Conversions
#include <string>
// Number to string
string s1 = to_string(42);
string s2 = to_string(3.14);
// String to number
int i = stoi("42");
long l = stol("42");
long long ll = stoll("42");
float f = stof("3.14");
double d = stod("3.14");
10. Performance Tips
- Use
reserve()when you know the approximate size - Use
emplace_back()instead ofpush_back()for complex objects - Pass by reference to avoid copies:
const vector<int>& v - Use
ios_base::sync_with_stdio(false)andcin.tie(nullptr)for fast I/O - Prefer
vectoroverlist— cache locality matters more than asymptotic complexity for small n - Use
unordered_mapovermapwhen ordering isn’t needed - Use
arrayovervectorwhen size is fixed - Avoid
endl— use'\n'instead (endl flushes the buffer) - Use structured bindings (C++17) for cleaner code
- Use
bitheader (C++20):popcount,countr_zero,bit_ceil
This guide covers the essential STL you need for competitive programming and interviews. Master these, and you’ll write solutions faster and with fewer bugs.