C++ STL Syntax for DSA
The vector, string, map, set, heap and algorithm calls you type in every C++ coding round, with compiled snippets and the Big-O of each operation.
C++ is a great interview language once the STL is in your fingers: the containers are fast and the algorithms header saves a lot of typing. This sheet groups the calls that appear in almost every DSA problem by container, with a complexity table for each, so you can spend your thinking on the algorithm instead of on syntax.
Snippets assume C++17 and using namespace std; with the needed headers already included, as is usual in interview and contest code. Each one compiles and its comments show the expected output or contents. Tick an item once you can write it from memory, and read the bullets: they are the gotchas that cost points.
Arrays & vector
A growable array with O(1) indexing; the container you reach for first.
1Declare & initialise (1D / 2D)
Give a size and a fill value up front; a 2D grid is a vector of row vectors.
vector<int> v(5)is five zeros, butvector<int> v{5}is a single element 5.- Prefer
vectorover C arrays: it knows its size and can be passed or returned by value.
vector<int> a; // empty vector<int> b(5, 0); // [0, 0, 0, 0, 0] vector<int> c = {3, 1, 2}; // initializer list int r = 3, col = 4; vector<vector<int>> g(r, vector<int>(col, -1)); // 3 x 4 grid, all -1 int arr[4] = {}; // plain C array, all 0 cout << c.size() << " " << g.size() << " " << g[0].size(); // 3 3 4
2push_back, emplace_back, pop_back, back
Add or remove at the end in amortised O(1);
back()reads the last element.pop_back()returns nothing, so readback()first.- Calling
back()orpop_back()on an empty vector is undefined behaviour.
vector<int> v; v.push_back(1); v.push_back(2); v.emplace_back(3); // constructs in place: [1, 2, 3] cout << v.back() << " "; // 3 v.pop_back(); // [1, 2] vector<pair<int, int>> ps; ps.emplace_back(4, 5); // no make_pair needed cout << v.size(); // 2
3size, empty & the unsigned trapGotcha
size()returns an unsignedsize_t, sov.size() - 1on an empty vector wraps to a huge number.- Cast once:
int n = v.size();and loop withn. - Check
v.empty()rather thanv.size() == 0.
vector<int> v; cout << v.empty() << " " << v.size() << "\n"; // 1 0 // for (int i = 0; i < v.size() - 1; i++) // BUG: size() - 1 wraps around int n = v.size(); // cast to int once for (int i = 0; i < n - 1; i++) {} // safe cout << n - 1; // -1
- Cast once:
4Iterate: index, range-for, auto&
Use an index loop when you need
i, range-for when you only need values, andauto&to modify in place.- Plain
for (int x : v)works on copies; changes are lost. - Use
const auto&for strings or nested vectors to avoid copying each element.
vector<int> v = {4, 5, 6}; for (int i = 0; i < (int)v.size(); i++) cout << v[i] << " "; // 4 5 6 for (int x : v) x *= 2; // copies: v unchanged for (auto& x : v) x *= 2; // references: v = [8, 10, 12] for (const auto& x : v) cout << x << " "; // 8 10 12
- Plain
5fill, resize, assign
Reset every slot (e.g. to infinity or -1) or change the length of an existing vector.
resizekeeps existing elements and pads with the default;assignreplaces everything.memsetonly works for 0 and -1 on ints; preferfillor the constructor.
vector<int> dist(4); fill(dist.begin(), dist.end(), INT_MAX); // all INT_MAX vector<int> v = {1, 2, 3}; v.resize(5); // [1, 2, 3, 0, 0] v.resize(2); // [1, 2] v.assign(3, 7); // [7, 7, 7] vector<vector<int>> memo(3, vector<int>(3, -1)); // 2D: fill at construction cout << v[0] + v[2] << " " << memo[2][2]; // 14 -1
6sort & reverse
Both take an iterator range
[first, last), so you can sort or reverse just a part.sortis not stable; usestable_sortwhen equal items must keep their order.
vector<int> v = {5, 2, 9, 1}; sort(v.begin(), v.end()); // [1, 2, 5, 9] reverse(v.begin(), v.end()); // [9, 5, 2, 1] sort(v.begin(), v.begin() + 2); // first two only: [5, 9, 2, 1] cout << v[0] << " " << v[1]; // 5 9
7accumulate, max_element, min_element
Sum a range and find the largest or smallest element without writing a loop.
- The start value sets the type:
accumulate(..., 0)sums in int and can overflow; pass0LL. max_elementreturns an iterator: use*itfor the value,it - v.begin()for the index.
vector<int> v = {3, 7, 2}; int sum = accumulate(v.begin(), v.end(), 0); // 12 long long big = accumulate(v.begin(), v.end(), 0LL); // sums in long long int mx = *max_element(v.begin(), v.end()); // 7 int idx = min_element(v.begin(), v.end()) - v.begin(); // 2 (index of the min) cout << sum << " " << big << " " << mx << " " << idx; // 12 12 7 2
- The start value sets the type:
8iota, count, find
iotafills 0, 1, 2, ...;countandfinddo a linear scan for a value.findreturnsend()when the value is missing; always compare before using it.
Pattern: Union-Find →vector<int> parent(5); iota(parent.begin(), parent.end(), 0); // [0, 1, 2, 3, 4] (DSU init) vector<int> v = {1, 2, 2, 3}; cout << count(v.begin(), v.end(), 2) << " "; // 2 auto it = find(v.begin(), v.end(), 3); if (it != v.end()) cout << it - v.begin(); // 3 (its index)
| Operation | Code | Time |
|---|---|---|
| Access / update | v[i], v[i] = x | O(1) |
| Append / remove last | push_back, emplace_back, pop_back | O(1) amortised |
| Insert / erase in middle | v.insert(it, x), v.erase(it) | O(n) |
| Linear search | find, count | O(n) |
| Sum / min / max | accumulate, min_element, max_element | O(n) |
| Fill / reverse | fill, reverse | O(n) |
| Sort | sort(v.begin(), v.end()) | O(n log n) |
string
Mutable characters: index them, append to them, compare with ==.
9size, s[i], +=, push_back
A C++
stringis mutable, so you can change characters and append in place.s += tappends in place;s = s + tbuilds a new string every time (O(n) per call in a loop).size()andlength()are identical.
string s = "code"; cout << s.size() << " " << s.length() << " " << s[0] << "\n"; // 4 4 c s[0] = 'C'; // "Code" s += "rs"; // "Coders" s.push_back('!'); // "Coders!" s.pop_back(); // "Coders" cout << s << " " << s.back(); // Coders s
10substr(pos, len)Gotcha
Copies
lencharacters starting atpos; leave outlento go to the end.- The second argument is a LENGTH, not an end index. For
[i, j)writes.substr(i, j - i). - It copies, so it costs O(len); avoid it inside tight loops.
string s = "interview"; cout << s.substr(5, 3) << "\n"; // vie (start 5, length 3) cout << s.substr(5) << "\n"; // view (to the end) int i = 0, j = 5; // want the half-open range [i, j) cout << s.substr(i, j - i); // inter
- The second argument is a LENGTH, not an end index. For
11find, rfind & npos
Returns the index of the first match, or
string::nposwhen there is none.- Compare the result with
string::npos; storing it in anintand testing< 0is fragile. - An optional second argument sets where the search starts.
string s = "banana"; cout << s.find("an") << " "; // 1 cout << s.find("an", 2) << " "; // 3 (search from index 2) cout << s.rfind('a') << " "; // 5 (last match) if (s.find("xyz") == string::npos) cout << "missing"; // missing
- Compare the result with
12to_string, stoi, stoll
Convert between numbers and text; single digits convert with
- '0'.stoithrows on non-numeric input and on values outside the int range; usestollfor big values.
int n = stoi("42"); long long big = stoll("9000000000"); string s = to_string(n + 1); // "43" int d = '7' - '0'; // 7 char ch = '0' + 5; // '5' cout << s << " " << big + 1 << " " << d << " " << ch; // 43 9000000001 7 5
13char arithmetic: c - 'a'
Characters are small integers, so
c - 'a'maps 'a'..'z' to 0..25 for a fixed-size count array.- A
vector<int>(26)orint[26]beats a hash map for lowercase-only input. - Cast to
intto print a character code.
Practice: Valid Anagram →string s = "abca"; vector<int> freq(26, 0); for (char c : s) freq[c - 'a']++; // 'a' -> 0, 'b' -> 1, ... cout << freq[0] << " " << freq[2] << "\n"; // 2 1 char next = 'a' + 1; // 'b' cout << next << " " << (int)'A'; // b 65
- A
14isdigit, isalpha, tolower
Classify or convert single characters, e.g. to clean input for a palindrome check.
tolower/toupperreturn anint: cast tocharbefore printing or appending.- Also handy:
isalnum,isupper,islower,isspace.
string s = "A1b!"; for (char c : s) { if (isdigit(c)) cout << "digit "; else if (isalpha(c)) cout << (char)tolower(c) << " "; else cout << "other "; } // a digit b other cout << (bool)isalnum('z') << " " << (char)toupper('q'); // 1 Q
15reverse & sort a string
Strings work with the same algorithms as vectors; a sorted copy is a classic anagram key.
string r(s.rbegin(), s.rend())builds a reversed copy without touchings.
Practice: Group Anagrams →string s = "stack"; string r(s.rbegin(), s.rend()); // reversed copy: "kcats" reverse(s.begin(), s.end()); // in place: "kcats" string t = "listen"; sort(t.begin(), t.end()); // "eilnst" cout << s << " " << t << " " << r; // kcats eilnst kcats
16Split with stringstream / getline
C++ has no
split; read words with>>or pieces between a delimiter withgetline.>>skips any run of spaces;getline(ss, tok, ',')keeps empty tokens.
stringstream ss("the quick brown fox"); string word; vector<string> words; while (ss >> word) words.push_back(word); // split on whitespace cout << words.size() << "\n"; // 4 stringstream ss2("a,b,,c"); string tok; while (getline(ss2, tok, ',')) cout << "[" << tok << "]"; // [a][b][][c]
17Comparing strings
==,<and friends compare by content, character by character (lexicographic order).a.compare(b)returns negative, zero or positive, like Java'scompareTo.- Uppercase sorts before lowercase because 'Z' (90) < 'a' (97).
string a = "apple", b = "apricot"; cout << (a == b) << " " << (a < b) << "\n"; // 0 1 cout << (a.compare(b) < 0) << "\n"; // 1 string c = "Zoo"; cout << (c < a); // 1
| Operation | Code | Time |
|---|---|---|
| Read / write a char | s[i], s.back() | O(1) |
| Append | s += t, s.push_back(c) | O(|t|) amortised |
| Substring | s.substr(i, len) | O(len) |
| Search | s.find(t) | O(n*m) worst |
| Compare | s == t, s < t | O(n) |
| Concatenate into new string | s = s + t | O(n + m) |
| Reverse / sort | reverse, sort | O(n) / O(n log n) |
unordered_map & map
Hash map for O(1) average lookups; map when you need the keys in order.
18insert, [] and the silent insertGotcha
m[k] = vinserts or overwrites;insertonly adds a key that is not there yet.- Just READING
m[k]insertskwith a default value (0, empty string, ...) if it is missing. []does not compile on aconstmap; usefindorat(k)(which throws when missing).
unordered_map<string, int> m; m["a"] = 1; // insert or overwrite m.insert({"b", 2}); // insert only if the key is new m.insert({"a", 99}); // ignored: "a" already exists cout << m["a"] << " "; // 1 cout << m["zzz"] << " "; // 0 <- this line just inserted "zzz"! cout << m.size(); // 3
- Just READING
19count, find, contains
Test for a key without inserting it;
findalso hands you the value in the same lookup.countreturns 0 or 1 for maps.- C++20 adds
m.contains(k); many judges still compile as C++17, socountis the safe habit.
Practice: Two Sum →unordered_map<int, int> m = {{1, 10}, {2, 20}}; if (m.count(1)) cout << "has1 "; // has1 auto it = m.find(2); if (it != m.end()) cout << it->second; // 20 (it->first is the key) // C++20: if (m.contains(1)) ...
20erase
Remove by key (returns how many were removed) or by iterator.
- Erasing a missing key is fine and returns 0.
unordered_map<int, int> m = {{1, 10}, {2, 20}, {3, 30}}; cout << m.erase(2) << " "; // 1 (one key removed) auto it = m.find(3); if (it != m.end()) m.erase(it); // by iterator cout << m.size() << " " << m.count(2); // 1 0
21Iterate with structured bindings
for (auto& [k, v] : m)unpacks each key/value pair (C++17).unordered_maporder is unspecified;mapvisits keys in sorted order.- Don't insert or erase while range-iterating over the same map.
map<string, int> m = {{"b", 2}, {"a", 1}}; for (auto& [k, v] : m) cout << k << "=" << v << " "; // a=1 b=2 for (auto& p : m) p.second *= 10; // older style: p.first / p.second cout << m["b"]; // 20
22Frequency counting
freq[x]++works because a missing key starts at 0; here the silent insert is exactly what you want.- Value-initialised ints are 0, so no
getOrDefaultis needed.
Practice: Top K Frequent Elements →vector<int> nums = {3, 1, 3, 2, 3}; unordered_map<int, int> freq; for (int x : nums) freq[x]++; cout << freq[3] << " " << freq[2] << " " << freq.size(); // 3 1 3
- Value-initialised ints are 0, so no
23Grouping into vectors
Map a key to a
vectorandpush_backintom[key]; the empty vector is created on first use.- Use a sorted copy of each word as the key to group anagrams.
Practice: Group Anagrams →vector<string> words = {"eat", "tea", "tan", "ate"}; unordered_map<string, vector<string>> groups; for (auto& w : words) { string key = w; sort(key.begin(), key.end()); groups[key].push_back(w); } cout << groups["aet"].size() << " " << groups.size(); // 3 2
24map: lower_bound & upper_bound
A
mapis a sorted tree, so it can find the nearest key to any value in O(log n).lower_bound(x): first key >= x.upper_bound(x): first key > x.- Floor (largest key <= x) is
prev(upper_bound(x)), after checking it is notbegin().
Pattern: Ordered set →map<int, string> m = {{10, "a"}, {20, "b"}, {30, "c"}}; auto it = m.lower_bound(15); // first key >= 15 cout << it->first << " "; // 20 it = m.upper_bound(20); // first key > 20 cout << it->first << " "; // 30 auto f = m.upper_bound(25); // floor(25): step back once if (f != m.begin()) cout << prev(f)->first; // 20
25Smallest & largest key: begin, prev(end())
The first entry has the smallest key and the last has the largest.
end()is one past the last element; never dereference it.- Check
!m.empty()before touchingbegin()orprev(m.end()).
map<int, int> m = {{5, 1}, {1, 2}, {9, 3}}; cout << m.begin()->first << " "; // 1 (smallest) cout << prev(m.end())->first << " "; // 9 (largest) cout << m.rbegin()->first << "\n"; // 9 (same thing) m.erase(m.begin()); // pop the smallest cout << m.begin()->first; // 5
| Operation | unordered_map | map |
|---|---|---|
| Insert / update | O(1) avg, O(n) worst | O(log n) |
| Lookup: find, count, [] | O(1) avg, O(n) worst | O(log n) |
| Erase by key | O(1) avg | O(log n) |
| lower_bound / upper_bound | not available | O(log n) |
| Smallest / largest key | O(n) scan | O(1) via begin / rbegin |
| Iterate all | O(n), any order | O(n), sorted by key |
unordered_set & set
Unique values: hashing for speed, a tree when you need order.
26insert, count, erase
insertreturns a pair{iterator, bool}; the bool tells you whether the value was new.- The returned bool is a neat one-step duplicate check.
Practice: Contains Duplicate →unordered_set<int> s; auto [it, added] = s.insert(5); cout << added << " "; // 1 (new) cout << s.insert(5).second << " "; // 0 (duplicate ignored) cout << s.count(5) << " "; // 1 s.erase(5); cout << s.size(); // 0
27Build from a vector & de-duplicate
Construct a set straight from a range, or remove duplicates from a vector with sort + unique + erase.
uniqueonly shifts duplicates to the end and returns the new end;eraseactually removes them.
vector<int> v = {3, 1, 3, 2, 1}; unordered_set<int> seen(v.begin(), v.end()); // 3 distinct values set<int> ordered(v.begin(), v.end()); // {1, 2, 3} cout << seen.size() << " " << *ordered.begin() << " "; // 3 1 sort(v.begin(), v.end()); v.erase(unique(v.begin(), v.end()), v.end()); // [1, 2, 3] cout << v.size(); // 3
28set: ordered, begin/rbegin, lower_bound
A
setkeeps values sorted, giving O(log n) min, max and nearest-value queries.- Call the member
s.lower_bound(x); the freestd::lower_boundon a set is O(n).
set<int> s = {40, 10, 30, 20}; cout << *s.begin() << " " << *s.rbegin() << " "; // 10 40 auto it = s.lower_bound(25); // first value >= 25 cout << *it << " "; // 30 s.erase(s.begin()); // remove smallest cout << *s.begin(); // 20
- Call the member
29multiset: erase(find(x)) vs erase(x)Gotcha
A
multisetkeeps duplicates in sorted order, e.g. for a sliding-window median or min/max.ms.erase(x)removes EVERY copy of x;ms.erase(ms.find(x))removes just one.- Only call
erase(find(x))when x is present; erasingend()is undefined.
multiset<int> ms = {5, 5, 5, 7}; ms.erase(ms.find(5)); // removes ONE 5 cout << ms.count(5) << " "; // 2 ms.erase(5); // removes ALL remaining 5s cout << ms.size(); // 1
| Operation | unordered_set | set / multiset |
|---|---|---|
| Insert | O(1) avg | O(log n) |
| Membership: count / find | O(1) avg | O(log n) |
| Erase one | O(1) avg | O(log n) |
| Min / max | O(n) scan | O(1) via begin / rbegin |
| lower_bound / upper_bound | not available | O(log n) |
| Iterate all | O(n), any order | O(n), sorted |
priority_queue (heap)
Always hands you the largest (or smallest) element fast.
30Max-heap (the default)
priority_queue<int>keeps the LARGEST element on top; the opposite of Java's default.pop()returns void: readtop()first, then pop.top()orpop()on an empty heap is undefined behaviour.
priority_queue<int> pq; for (int x : {3, 9, 1}) pq.push(x); cout << pq.top() << " "; // 9 pq.pop(); // removes 9, returns nothing cout << pq.top() << " " << pq.size(); // 3 2
31Min-heap with greater<int>
Spell out the container and pass
greater<int>to flip the order.- Shortcut: push
-xinto a max-heap and negate when you read it back.
priority_queue<int, vector<int>, greater<int>> pq; for (int x : {3, 9, 1}) pq.push(x); while (!pq.empty()) { cout << pq.top() << " "; // 1 3 9 pq.pop(); }
- Shortcut: push
32Heap of pairs (Dijkstra)
Pairs compare by
.first, then.second, so{dist, node}pairs order themselves.greater<>(empty brackets) deduces the element type for you.
Pattern: Shortest path →// {distance, node} priority_queue<pair<int, int>, vector<pair<int, int>>, greater<>> pq; pq.push({5, 1}); pq.push({2, 3}); pq.push({2, 0}); auto [d, u] = pq.top(); // smallest distance, ties by node cout << d << " " << u; // 2 0
33Custom comparator: lambda + decltype
For any other order, write a lambda and pass its type with
decltype.- The comparator answers 'should a sit BELOW b?', so
>gives a min-heap. - Before C++20 you must also pass the lambda to the constructor:
pq(cmp).
// min-heap on .second auto cmp = [](const pair<int, int>& a, const pair<int, int>& b) { return a.second > b.second; }; priority_queue<pair<int, int>, vector<pair<int, int>>, decltype(cmp)> pq(cmp); pq.push({1, 50}); pq.push({2, 10}); pq.push({3, 30}); cout << pq.top().first; // 2 (smallest .second)
- The comparator answers 'should a sit BELOW b?', so
34Top-k with a size-k min-heap
Keep only k elements; the smallest of them sits on top and is the k-th largest overall.
- O(n log k) time and O(k) space, better than sorting when k is small.
Pattern: Heap / Top-K →vector<int> nums = {4, 1, 7, 3, 8, 5}; int k = 3; priority_queue<int, vector<int>, greater<int>> pq; for (int x : nums) { pq.push(x); if ((int)pq.size() > k) pq.pop(); // drop the smallest } cout << pq.top(); // 5 (3rd largest)
| Operation | Code | Time |
|---|---|---|
| Insert | pq.push(x), pq.emplace(...) | O(log n) |
| Remove top | pq.pop() | O(log n) |
| Read top | pq.top() | O(1) |
| Size / empty | pq.size(), pq.empty() | O(1) |
| Build from a range | priority_queue<int> pq(v.begin(), v.end()) | O(n) |
| Remove arbitrary / search | not supported (use set / multiset) | - |
queue, deque & stack
FIFO for BFS, LIFO for matching brackets, both ends for sliding windows.
35stack: push, top, pop
Last in, first out;
top()reads,pop()removes.- Like the heap,
pop()returns void. - A
vectorwithpush_back/back/pop_backworks as a stack too.
Practice: Valid Parentheses →stack<int> st; st.push(1); st.push(2); cout << st.top() << " "; // 2 st.pop(); cout << st.top() << " " << st.size(); // 1 1
- Like the heap,
36queue: push, front, back, pop
First in, first out;
front()is the next element to leave.- There is no
top()on a queue; it isfront().
queue<int> q; q.push(1); q.push(2); q.push(3); cout << q.front() << " " << q.back() << " "; // 1 3 q.pop(); // removes 1 cout << q.front(); // 2
- There is no
37BFS skeleton
Push the start, then repeatedly pop a node and push its unvisited neighbours.
- Mark a node visited when you PUSH it, not when you pop it, or it gets queued many times.
- Level by level:
for (int sz = q.size(); sz > 0; sz--)inside the while loop.
Pattern: Graph BFS →vector<vector<int>> adj = {{1, 2}, {3}, {3}, {}}; // node -> neighbours vector<int> dist(adj.size(), -1); queue<int> q; q.push(0); dist[0] = 0; while (!q.empty()) { int u = q.front(); q.pop(); for (int v : adj[u]) if (dist[v] == -1) { dist[v] = dist[u] + 1; q.push(v); } } cout << dist[3]; // 2
38deque: both ends
A double-ended queue: O(1) push and pop at the front and the back, plus indexing.
stackandqueueare thin wrappers that use adequeunderneath.
deque<int> dq = {2, 3}; dq.push_front(1); // [1, 2, 3] dq.push_back(4); // [1, 2, 3, 4] dq.pop_front(); // [2, 3, 4] dq.pop_back(); // [2, 3] cout << dq.front() << " " << dq.back() << " " << dq[0]; // 2 3 2
39Sliding window maximum (monotonic deque)
Store indices with decreasing values; the front is always the current window's maximum.
- Pop the front when it leaves the window, pop the back while it is smaller than the new value.
- Each index enters and leaves once: O(n) overall.
Practice: Sliding Window Maximum →vector<int> a = {1, 3, -1, -3, 5, 3}; int k = 3; deque<int> dq; // indices for (int i = 0; i < (int)a.size(); i++) { if (!dq.empty() && dq.front() <= i - k) dq.pop_front(); // left the window while (!dq.empty() && a[dq.back()] <= a[i]) dq.pop_back(); // can never win dq.push_back(i); if (i >= k - 1) cout << a[dq.front()] << " "; // 3 3 5 5 }
| Operation | Code | Time |
|---|---|---|
| Stack push / pop / top | push(x), pop(), top() | O(1) |
| Queue push / pop / front | push(x), pop(), front(), back() | O(1) |
| Deque either end | push_front/back, pop_front/back | O(1) |
| Deque index | dq[i] | O(1) |
| Deque insert in middle | dq.insert(it, x) | O(n) |
| Search | no member; std::find on deque | O(n) |
Sorting & comparators
Describe the order once and reuse it for sort, heaps, sets and binary search.
40sort with a lambda
Pass a comparator that returns true when
amust come beforeb.- It must be a strict ordering: use
<, never<=(equal elements can then crashsort). - Capture with
[&]to sort indices by an outside array.
vector<int> v = {5, -2, 3, -8}; sort(v.begin(), v.end(), [](int a, int b) { return abs(a) < abs(b); }); // [-2, 3, 5, -8] vector<int> score = {30, 10, 20}, idx = {0, 1, 2}; sort(idx.begin(), idx.end(), [&](int i, int j) { return score[i] < score[j]; }); cout << v[0] << " " << idx[0] << " " << idx[2]; // -2 1 0
- It must be a strict ordering: use
41Descending order
Use
greater<int>(), reverse iterators, or a lambda with>.- The same
greater<>flips the order ofpriority_queue,setandmaptoo.
vector<int> v = {2, 9, 4}; sort(v.begin(), v.end(), greater<int>()); // [9, 4, 2] sort(v.rbegin(), v.rend()); // also descending sort(v.begin(), v.end(), [](int a, int b) { return a > b; }); // same again cout << v[0] << " " << v[2]; // 9 2
- The same
42Sorting pairs, by first or by second
Pairs (and vectors) sort lexicographically by default: first, then second.
vector<vector<int>>intervals also sort by start, then end, with a plainsort.
Practice: Merge Intervals →vector<pair<int, int>> p = {{2, 5}, {1, 9}, {2, 1}}; sort(p.begin(), p.end()); // [(1,9), (2,1), (2,5)] sort(p.begin(), p.end(), [](auto& a, auto& b) { return a.second < b.second; }); // by second: [(2,1), (2,5), (1,9)] cout << p[0].first << " " << p[2].second; // 2 9
43stable_sort
Like
sort, but elements that compare equal keep their original relative order.- Useful for multi-key sorts: sort by the minor key first, then
stable_sortby the major key.
vector<pair<string, int>> s = {{"bob", 2}, {"amy", 1}, {"cat", 2}}; stable_sort(s.begin(), s.end(), [](auto& a, auto& b) { return a.second < b.second; }); for (auto& [name, g] : s) cout << name << " "; // amy bob cat
- Useful for multi-key sorts: sort by the minor key first, then
44Custom struct with operator<
Define
operator<once andsort,setandmapall use it automatically.- Make both the parameter and the method
const, or containers will refuse to compile. - A
priority_queueof the struct uses the same<, so it becomes a max-heap on that key.
struct Job { int start, end; bool operator<(const Job& o) const { return end < o.end; } // by end time }; vector<Job> jobs = {{1, 9}, {2, 4}, {3, 6}}; sort(jobs.begin(), jobs.end()); cout << jobs[0].end << " " << jobs[2].end; // 4 9
- Make both the parameter and the method
45nth_element (k-th smallest)
Puts the k-th smallest element at index k in average O(n), without fully sorting.
- Afterwards, everything left of k is <= v[k] and everything right is >= v[k], in no particular order.
Practice: Kth Largest Element →vector<int> v = {7, 2, 9, 4, 1}; int k = 2; // 0-based: 3rd smallest nth_element(v.begin(), v.begin() + k, v.end()); cout << v[k]; // 4
46binary_search, lower_bound, upper_bound
O(log n) searches on an already sorted range.
lower_bound: first element >= x.upper_bound: first element > x. Both returnend()if none.upper_bound - lower_boundcounts the copies of x.
Pattern: Binary search →vector<int> v = {1, 3, 3, 3, 8}; cout << binary_search(v.begin(), v.end(), 3) << "\n"; // 1 (found) int lo = lower_bound(v.begin(), v.end(), 3) - v.begin(); // 1 int hi = upper_bound(v.begin(), v.end(), 3) - v.begin(); // 4 cout << lo << " " << hi << " " << hi - lo; // 1 4 3
| Operation | Code | Time |
|---|---|---|
| Sort | sort(b, e, cmp) | O(n log n) |
| Stable sort | stable_sort(b, e, cmp) | O(n log n) |
| k-th element | nth_element(b, b + k, e) | O(n) average |
| Search sorted range | binary_search, lower_bound, upper_bound | O(log n) |
| Check sorted | is_sorted(b, e) | O(n) |
pair, tuple & structs
Bundle values together and define the node types for list and tree problems.
47pair basics
Holds two values, read with
.firstand.second; braces ormake_pairbuild one.- Pairs compare by
.first, then.second, so they sort and heap without a comparator.
pair<int, string> p = {1, "one"}; auto q = make_pair(2, string("two")); cout << p.first << " " << q.second << "\n"; // 1 two cout << (p < q); // 1
- Pairs compare by
48tuple, tie & structured bindings
A tuple holds any number of values; unpack it with
auto [a, b, c]ortie.- Use
tieto assign into variables that already exist. - For more than three fields, a small named struct reads better than
get<3>(t).
tuple<int, int, string> t = {3, 4, "x"}; cout << get<0>(t) << " " << get<2>(t) << "\n"; // 3 x auto [a, b, c] = t; // C++17 int x, y; tie(x, y) = make_pair(7, 8); cout << a + b << " " << x << " " << y; // 7 7 8
- Use
49ListNode + dummy head
A dummy node in front of the list removes the special case for inserting or deleting the head.
- LeetCode already defines
ListNode; in your own file put it at the top level. - Use
nullptr, notNULLor 0, for empty pointers.
Pattern: Linked list →struct ListNode { int val; ListNode* next; ListNode(int x) : val(x), next(nullptr) {} }; ListNode dummy(0); // lives on the stack ListNode* tail = &dummy; for (int x : {1, 2, 3}) { tail->next = new ListNode(x); tail = tail->next; } ListNode* head = dummy.next; // the real head for (ListNode* cur = head; cur; cur = cur->next) cout << cur->val << " "; // 1 2 3
- LeetCode already defines
50TreeNode + a recursive lambda
Each node owns two child pointers; recursion is the natural way to walk the tree.
- A lambda cannot call itself through
auto; wrap it infunction<...>or write a normal function.
Pattern: Tree DFS →struct TreeNode { int val; TreeNode *left, *right; TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} }; TreeNode* root = new TreeNode(1); root->left = new TreeNode(2); root->left->left = new TreeNode(3); function<int(TreeNode*)> height = [&](TreeNode* n) { return n ? 1 + max(height(n->left), height(n->right)) : 0; }; cout << height(root); // 3
- A lambda cannot call itself through
Handy utilities
Limits, overflow guards, modulo, bit tricks and fast I/O.
51Limits & long long overflow
An
intholds about +-2.1e9; anything bigger (sums, products, 1e5 * 1e5) needslong long.- Promote BEFORE the multiplication:
1LL * a * b, not(long long)(a * b). - Signed overflow is undefined behaviour in C++, not just a wrap-around.
int a = INT_MAX; // 2147483647 long long prod = 1LL * a * 2; // promote first // int bad = a * 2; // overflow: undefined behaviour cout << INT_MIN << " " << prod << "\n"; // -2147483648 4294967294 cout << LLONG_MAX; // 9223372036854775807
- Promote BEFORE the multiplication:
52Modulo 1e9+7
Reduce after every multiply or add so values never leave the
long longrange.%keeps the sign of the left side in C++; add MOD once more to stay non-negative.1e9 + 7is a double; it is exact here, but write1'000'000'007when in doubt.
const int MOD = 1e9 + 7; long long x = 1; for (int i = 0; i < 40; i++) x = x * 2 % MOD; // 2^40 mod MOD cout << x << "\n"; // 511620083 int a = 3, b = 5; cout << ((a - b) % MOD + MOD) % MOD; // 1000000005
53Bit tricks & builtins
Count, test and clear bits in O(1) with GCC/Clang builtins and a few classic masks.
__builtin_*are GCC/Clang only (C++20 addsstd::popcount); use thellversions for 64-bit.__builtin_ctz(0)is undefined. Power of two:x > 0 && (x & (x - 1)) == 0.
Pattern: Bit manipulation →int x = 12; // binary 1100 cout << __builtin_popcount(x) << " "; // 2 (set bits) cout << __builtin_ctz(x) << " "; // 2 (trailing zeros) cout << (x & (x - 1)) << " "; // 8 (lowest set bit cleared) cout << (x & -x) << " "; // 4 (lowest set bit alone) cout << ((x >> 2) & 1) << " " << (1 << 3); // 1 8
54min/max of many, swap, abs
min({a, b, c})takes an initializer list;swapexchanges two values.- Both arguments of
min/maxmust have the SAME type:max(x, 3LL)for along longx.
int a = 4, b = 9, c = 1; cout << min({a, b, c}) << " " << max({a, b, c}) << " "; // 1 9 swap(a, b); // a = 9, b = 4 cout << a << " "; // 9 long long big = 5; // max(big, 3) does not compile: long long vs int cout << max(big, 3LL) << " " << abs(-7); // 5 7
- Both arguments of
55gcd & lcm
gcdandlcmlive in<numeric>since C++17.- Older GCC code uses
__gcd(a, b); it is non-standard. gcd(0, x) == x, so 0 is a safe starting value when folding over a list.
cout << gcd(12, 18) << " " << lcm(4, 6) << " "; // 6 12 int g = 0; for (int x : {8, 12, 20}) g = gcd(g, x); // gcd of a list cout << g; // 4
- Older GCC code uses
56Fast I/O & headers
Two lines at the top of
mainmakecin/coutfast enough for large inputs.#include <bits/stdc++.h>pulls in the whole library; it is a GCC-only shortcut for contests (not MSVC or Clang with libc++).- After unsyncing, don't mix
scanf/printfwithcin/cout; prefer"\n"overendl, which flushes.
ios::sync_with_stdio(false); // unhook from C stdio cin.tie(nullptr); // don't flush cout before every cin int n = 0; cin >> n; vector<int> a(n); for (auto& x : a) cin >> x; cout << a.size() << "\n"; // n
57auto, iterators & lambdas
autolets the compiler pick the type; lambdas are inline functions that can capture local variables.auto s = "hi";is aconst char*, not astring.[&]captures by reference (sees later changes);[=]copies.
auto y = 5LL; // long long auto s = string("hi"); // string vector<int> v = {1, 2, 3}; for (auto it = v.begin(); it != v.end(); ++it) *it += 1; // [2, 3, 4] auto add = [&](int d) { for (auto& e : v) e += d; }; add(10); // [12, 13, 14] cout << v[0] << " " << sizeof(y) << " " << s.size(); // 12 8 2
| Name | Value | Use |
|---|---|---|
| INT_MAX / INT_MIN | 2147483647 / -2147483648 | Infinity sentinel for int |
| LLONG_MAX | 9223372036854775807 (~9.2e18) | Infinity for long long |
| 1e9 + 7 | 1000000007 | Usual answer modulus |
| int range | ~ +-2.1e9 | n * n overflows once n > ~46340 |
| long long range | ~ +-9.2e18 | Sums of up to ~1e9 values of 1e9 |
Quick tips
- Cast
size()tointbefore subtracting from it; unsigned wrap-around is the most common silent C++ bug. - Test membership with
countorfind; readingm[k]inserts the key. priority_queueis a max-heap by default, andpop()on stacks, queues and heaps returns nothing.- Use
long long(and1LL *) as soon as products or sums can pass about 2e9. - Say the complexity out loud:
unordered_mapis O(1) average,map/setare O(log n),substrandfindare linear.
Topic list based on the free Everything You Need to Get Placed checklist by Mohammad Fraz. Explanations and examples are original.