C++ STL Syntax for DSA

Quick revision of the STL you use in every problem⏱ 27 min readUpdated

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.

0 of 57 done

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.

  1. 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, but vector<int> v{5} is a single element 5.
    • Prefer vector over C arrays: it knows its size and can be passed or returned by value.
    cpp
    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
    
  2. 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 read back() first.
    • Calling back() or pop_back() on an empty vector is undefined behaviour.
    cpp
    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
    
  3. 3size, empty & the unsigned trapGotcha

    size() returns an unsigned size_t, so v.size() - 1 on an empty vector wraps to a huge number.

    • Cast once: int n = v.size(); and loop with n.
    • Check v.empty() rather than v.size() == 0.
    cpp
    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
    
  4. 4Iterate: index, range-for, auto&

    Use an index loop when you need i, range-for when you only need values, and auto& 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.
    cpp
    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
    
  5. 5fill, resize, assign

    Reset every slot (e.g. to infinity or -1) or change the length of an existing vector.

    • resize keeps existing elements and pads with the default; assign replaces everything.
    • memset only works for 0 and -1 on ints; prefer fill or the constructor.
    cpp
    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
    
  6. 6sort & reverse

    Both take an iterator range [first, last), so you can sort or reverse just a part.

    • sort is not stable; use stable_sort when equal items must keep their order.
    cpp
    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
    
  7. 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; pass 0LL.
    • max_element returns an iterator: use *it for the value, it - v.begin() for the index.
    cpp
    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
    
  8. 8iota, count, find

    iota fills 0, 1, 2, ...; count and find do a linear scan for a value.

    • find returns end() when the value is missing; always compare before using it.
    cpp
    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)
    
    Pattern: Union-Find →
OperationCodeTime
Access / updatev[i], v[i] = xO(1)
Append / remove lastpush_back, emplace_back, pop_backO(1) amortised
Insert / erase in middlev.insert(it, x), v.erase(it)O(n)
Linear searchfind, countO(n)
Sum / min / maxaccumulate, min_element, max_elementO(n)
Fill / reversefill, reverseO(n)
Sortsort(v.begin(), v.end())O(n log n)

string

Mutable characters: index them, append to them, compare with ==.

  1. 9size, s[i], +=, push_back

    A C++ string is mutable, so you can change characters and append in place.

    • s += t appends in place; s = s + t builds a new string every time (O(n) per call in a loop).
    • size() and length() are identical.
    cpp
    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
    
  2. 10substr(pos, len)Gotcha

    Copies len characters starting at pos; leave out len to go to the end.

    • The second argument is a LENGTH, not an end index. For [i, j) write s.substr(i, j - i).
    • It copies, so it costs O(len); avoid it inside tight loops.
    cpp
    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
    
  3. 11find, rfind & npos

    Returns the index of the first match, or string::npos when there is none.

    • Compare the result with string::npos; storing it in an int and testing < 0 is fragile.
    • An optional second argument sets where the search starts.
    cpp
    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
    
  4. 12to_string, stoi, stoll

    Convert between numbers and text; single digits convert with - '0'.

    • stoi throws on non-numeric input and on values outside the int range; use stoll for big values.
    cpp
    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
    
  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) or int[26] beats a hash map for lowercase-only input.
    • Cast to int to print a character code.
    cpp
    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
    
    Practice: Valid Anagram →
  6. 14isdigit, isalpha, tolower

    Classify or convert single characters, e.g. to clean input for a palindrome check.

    • tolower/toupper return an int: cast to char before printing or appending.
    • Also handy: isalnum, isupper, islower, isspace.
    cpp
    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
    
  7. 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 touching s.
    cpp
    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
    
    Practice: Group Anagrams →
  8. 16Split with stringstream / getline

    C++ has no split; read words with >> or pieces between a delimiter with getline.

    • >> skips any run of spaces; getline(ss, tok, ',') keeps empty tokens.
    cpp
    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]
    
  9. 17Comparing strings

    ==, < and friends compare by content, character by character (lexicographic order).

    • a.compare(b) returns negative, zero or positive, like Java's compareTo.
    • Uppercase sorts before lowercase because 'Z' (90) < 'a' (97).
    cpp
    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
    
OperationCodeTime
Read / write a chars[i], s.back()O(1)
Appends += t, s.push_back(c)O(|t|) amortised
Substrings.substr(i, len)O(len)
Searchs.find(t)O(n*m) worst
Compares == t, s < tO(n)
Concatenate into new strings = s + tO(n + m)
Reverse / sortreverse, sortO(n) / O(n log n)

unordered_map & map

Hash map for O(1) average lookups; map when you need the keys in order.

  1. 18insert, [] and the silent insertGotcha

    m[k] = v inserts or overwrites; insert only adds a key that is not there yet.

    • Just READING m[k] inserts k with a default value (0, empty string, ...) if it is missing.
    • [] does not compile on a const map; use find or at(k) (which throws when missing).
    cpp
    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
    
  2. 19count, find, contains

    Test for a key without inserting it; find also hands you the value in the same lookup.

    • count returns 0 or 1 for maps.
    • C++20 adds m.contains(k); many judges still compile as C++17, so count is the safe habit.
    cpp
    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)) ...
    
    Practice: Two Sum →
  3. 20erase

    Remove by key (returns how many were removed) or by iterator.

    • Erasing a missing key is fine and returns 0.
    cpp
    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
    
  4. 21Iterate with structured bindings

    for (auto& [k, v] : m) unpacks each key/value pair (C++17).

    • unordered_map order is unspecified; map visits keys in sorted order.
    • Don't insert or erase while range-iterating over the same map.
    cpp
    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
    
  5. 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 getOrDefault is needed.
    cpp
    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
    
    Practice: Top K Frequent Elements →
  6. 23Grouping into vectors

    Map a key to a vector and push_back into m[key]; the empty vector is created on first use.

    • Use a sorted copy of each word as the key to group anagrams.
    cpp
    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
    
    Practice: Group Anagrams →
  7. 24map: lower_bound & upper_bound

    A map is 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 not begin().
    cpp
    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
    
    Pattern: Ordered set →
  8. 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 touching begin() or prev(m.end()).
    cpp
    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
    
Operationunordered_mapmap
Insert / updateO(1) avg, O(n) worstO(log n)
Lookup: find, count, []O(1) avg, O(n) worstO(log n)
Erase by keyO(1) avgO(log n)
lower_bound / upper_boundnot availableO(log n)
Smallest / largest keyO(n) scanO(1) via begin / rbegin
Iterate allO(n), any orderO(n), sorted by key

unordered_set & set

Unique values: hashing for speed, a tree when you need order.

  1. 26insert, count, erase

    insert returns a pair {iterator, bool}; the bool tells you whether the value was new.

    • The returned bool is a neat one-step duplicate check.
    cpp
    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
    
    Practice: Contains Duplicate →
  2. 27Build from a vector & de-duplicate

    Construct a set straight from a range, or remove duplicates from a vector with sort + unique + erase.

    • unique only shifts duplicates to the end and returns the new end; erase actually removes them.
    cpp
    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
    
  3. 28set: ordered, begin/rbegin, lower_bound

    A set keeps values sorted, giving O(log n) min, max and nearest-value queries.

    • Call the member s.lower_bound(x); the free std::lower_bound on a set is O(n).
    cpp
    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
    
  4. 29multiset: erase(find(x)) vs erase(x)Gotcha

    A multiset keeps 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; erasing end() is undefined.
    cpp
    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
    
Operationunordered_setset / multiset
InsertO(1) avgO(log n)
Membership: count / findO(1) avgO(log n)
Erase oneO(1) avgO(log n)
Min / maxO(n) scanO(1) via begin / rbegin
lower_bound / upper_boundnot availableO(log n)
Iterate allO(n), any orderO(n), sorted

priority_queue (heap)

Always hands you the largest (or smallest) element fast.

  1. 30Max-heap (the default)

    priority_queue<int> keeps the LARGEST element on top; the opposite of Java's default.

    • pop() returns void: read top() first, then pop.
    • top() or pop() on an empty heap is undefined behaviour.
    cpp
    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
    
  2. 31Min-heap with greater<int>

    Spell out the container and pass greater<int> to flip the order.

    • Shortcut: push -x into a max-heap and negate when you read it back.
    cpp
    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();
    }
    
  3. 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.
    cpp
    // {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
    
    Pattern: Shortest path →
  4. 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).
    cpp
    // 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)
    
  5. 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.
    cpp
    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)
    
    Pattern: Heap / Top-K →
OperationCodeTime
Insertpq.push(x), pq.emplace(...)O(log n)
Remove toppq.pop()O(log n)
Read toppq.top()O(1)
Size / emptypq.size(), pq.empty()O(1)
Build from a rangepriority_queue<int> pq(v.begin(), v.end())O(n)
Remove arbitrary / searchnot supported (use set / multiset)-

queue, deque & stack

FIFO for BFS, LIFO for matching brackets, both ends for sliding windows.

  1. 35stack: push, top, pop

    Last in, first out; top() reads, pop() removes.

    • Like the heap, pop() returns void.
    • A vector with push_back/back/pop_back works as a stack too.
    cpp
    stack<int> st;
    st.push(1);
    st.push(2);
    cout << st.top() << " ";               // 2
    st.pop();
    cout << st.top() << " " << st.size();  // 1 1
    
    Practice: Valid Parentheses →
  2. 36queue: push, front, back, pop

    First in, first out; front() is the next element to leave.

    • There is no top() on a queue; it is front().
    cpp
    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
    
  3. 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.
    cpp
    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
    
    Pattern: Graph BFS →
  4. 38deque: both ends

    A double-ended queue: O(1) push and pop at the front and the back, plus indexing.

    • stack and queue are thin wrappers that use a deque underneath.
    cpp
    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
    
  5. 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.
    cpp
    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
    }
    
    Practice: Sliding Window Maximum →
OperationCodeTime
Stack push / pop / toppush(x), pop(), top()O(1)
Queue push / pop / frontpush(x), pop(), front(), back()O(1)
Deque either endpush_front/back, pop_front/backO(1)
Deque indexdq[i]O(1)
Deque insert in middledq.insert(it, x)O(n)
Searchno member; std::find on dequeO(n)

Sorting & comparators

Describe the order once and reuse it for sort, heaps, sets and binary search.

  1. 40sort with a lambda

    Pass a comparator that returns true when a must come before b.

    • It must be a strict ordering: use <, never <= (equal elements can then crash sort).
    • Capture with [&] to sort indices by an outside array.
    cpp
    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
    
  2. 41Descending order

    Use greater<int>(), reverse iterators, or a lambda with >.

    • The same greater<> flips the order of priority_queue, set and map too.
    cpp
    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
    
  3. 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 plain sort.
    cpp
    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
    
    Practice: Merge Intervals →
  4. 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_sort by the major key.
    cpp
    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
    
  5. 44Custom struct with operator<

    Define operator< once and sort, set and map all use it automatically.

    • Make both the parameter and the method const, or containers will refuse to compile.
    • A priority_queue of the struct uses the same <, so it becomes a max-heap on that key.
    cpp
    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
    
  6. 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.
    cpp
    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
    
    Practice: Kth Largest Element →
  7. 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 return end() if none.
    • upper_bound - lower_bound counts the copies of x.
    cpp
    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
    
    Pattern: Binary search →
OperationCodeTime
Sortsort(b, e, cmp)O(n log n)
Stable sortstable_sort(b, e, cmp)O(n log n)
k-th elementnth_element(b, b + k, e)O(n) average
Search sorted rangebinary_search, lower_bound, upper_boundO(log n)
Check sortedis_sorted(b, e)O(n)

pair, tuple & structs

Bundle values together and define the node types for list and tree problems.

  1. 47pair basics

    Holds two values, read with .first and .second; braces or make_pair build one.

    • Pairs compare by .first, then .second, so they sort and heap without a comparator.
    cpp
    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
    
  2. 48tuple, tie & structured bindings

    A tuple holds any number of values; unpack it with auto [a, b, c] or tie.

    • Use tie to assign into variables that already exist.
    • For more than three fields, a small named struct reads better than get<3>(t).
    cpp
    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
    
  3. 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, not NULL or 0, for empty pointers.
    cpp
    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
    
    Pattern: Linked list →
  4. 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 in function<...> or write a normal function.
    cpp
    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
    
    Pattern: Tree DFS →

Handy utilities

Limits, overflow guards, modulo, bit tricks and fast I/O.

  1. 51Limits & long long overflow

    An int holds about +-2.1e9; anything bigger (sums, products, 1e5 * 1e5) needs long 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.
    cpp
    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
    
  2. 52Modulo 1e9+7

    Reduce after every multiply or add so values never leave the long long range.

    • % keeps the sign of the left side in C++; add MOD once more to stay non-negative.
    • 1e9 + 7 is a double; it is exact here, but write 1'000'000'007 when in doubt.
    cpp
    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
    
  3. 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 adds std::popcount); use the ll versions for 64-bit.
    • __builtin_ctz(0) is undefined. Power of two: x > 0 && (x & (x - 1)) == 0.
    cpp
    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
    
    Pattern: Bit manipulation →
  4. 54min/max of many, swap, abs

    min({a, b, c}) takes an initializer list; swap exchanges two values.

    • Both arguments of min/max must have the SAME type: max(x, 3LL) for a long long x.
    cpp
    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
    
  5. 55gcd & lcm

    gcd and lcm live 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.
    cpp
    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
    
  6. 56Fast I/O & headers

    Two lines at the top of main make cin/cout fast 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/printf with cin/cout; prefer "\n" over endl, which flushes.
    cpp
    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
    
  7. 57auto, iterators & lambdas

    auto lets the compiler pick the type; lambdas are inline functions that can capture local variables.

    • auto s = "hi"; is a const char*, not a string.
    • [&] captures by reference (sees later changes); [=] copies.
    cpp
    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
    
NameValueUse
INT_MAX / INT_MIN2147483647 / -2147483648Infinity sentinel for int
LLONG_MAX9223372036854775807 (~9.2e18)Infinity for long long
1e9 + 71000000007Usual answer modulus
int range~ +-2.1e9n * n overflows once n > ~46340
long long range~ +-9.2e18Sums of up to ~1e9 values of 1e9

Quick tips

  • Cast size() to int before subtracting from it; unsigned wrap-around is the most common silent C++ bug.
  • Test membership with count or find; reading m[k] inserts the key.
  • priority_queue is a max-heap by default, and pop() on stacks, queues and heaps returns nothing.
  • Use long long (and 1LL *) as soon as products or sums can pass about 2e9.
  • Say the complexity out loud: unordered_map is O(1) average, map/set are O(log n), substr and find are linear.

Topic list based on the free Everything You Need to Get Placed checklist by Mohammad Fraz. Explanations and examples are original.