Skip to content

02 — STL Containers

Comprehensive reference for every STL container — methods, complexity, comparators, and gotchas. Covers sequence, ordered, unordered, adaptor, and special containers, plus GNU policy-based data structures.


Complexity Quick-Reference

Container Access Search Insert Erase Ordered?
vector O(1) O(n) O(1) amort. tail / O(n) mid O(1) tail / O(n) mid No
deque O(1) O(n) O(1) amort. both ends / O(n) mid O(1) ends / O(n) mid No
list O(n) O(n) O(1) given iterator O(1) given iterator No
array O(1) O(n) N/A (fixed size) N/A No
set / map O(log n) O(log n) O(log n) O(log n) Yes
multiset / multimap O(log n) O(log n) O(log n) O(log n) Yes
unordered_set / unordered_map O(1) avg O(1) avg O(1) avg / O(n) worst O(1) avg No
priority_queue O(1) top O(n) O(log n) O(log n) Heap
stack / queue O(1) top/front O(1) O(1) No
bitset O(1) O(n/w) O(1) O(1) No

std::vector

The workhorse sequence container. Contiguous memory, cache-friendly, amortized O(1) push_back.

#include <vector>

std::vector<int> v;                        // empty
std::vector<int> v2(5, 0);                // size 5, filled with 0
std::vector<int> v3 = {1, 2, 3, 4, 5};   // initializer list
std::vector<int> v4(v3.begin(), v3.end()); // copy from range

// Size and capacity
v3.size();       // 5  — number of elements
v3.capacity();   // >= 5 — allocated slots (implementation-defined)
v3.empty();      // false
v3.resize(8, 0); // extend to size 8, new elements = 0
v3.reserve(100); // allocate memory for 100 elements without changing size
v3.shrink_to_fit(); // release excess capacity

// Element access
v3[2];           // unchecked — UB if out-of-range
v3.at(2);        // checked — throws std::out_of_range
v3.front();      // first element
v3.back();       // last element
v3.data();       // raw pointer to underlying array

// Modifiers
v3.push_back(6);            // add to end
v3.emplace_back(7);         // construct in-place at end (prefer over push_back for objects)
v3.pop_back();              // remove from end
v3.insert(v3.begin()+1, 99); // insert 99 at index 1 — O(n)
v3.erase(v3.begin()+2);     // erase element at index 2 — O(n)
v3.erase(v3.begin(), v3.begin()+2); // erase range [0,2) — O(n)
v3.clear();                 // remove all elements, keep capacity

// 2D vector
std::vector<std::vector<int>> grid(3, std::vector<int>(4, 0)); // 3x4 grid of zeros

// Iteration
for (int x : v3) { /* range-for */ }
for (auto it = v3.begin(); it != v3.end(); ++it) { /* iterator */ }
for (int i = 0; i < (int)v3.size(); ++i) { /* index — cast to int to avoid signed/unsigned warning */ }

// Important: iterator invalidation
// push_back/reserve CAN invalidate ALL iterators if reallocation occurs
// insert/erase invalidate iterators at and after the point of change

std::deque

Double-ended queue. O(1) push/pop at both ends, O(1) random access, but slower than vector due to non-contiguous memory.

#include <deque>

std::deque<int> dq = {1, 2, 3};

dq.push_back(4);    // add to end
dq.push_front(0);   // add to front — O(1)
dq.pop_back();      // remove from end
dq.pop_front();     // remove from front — O(1)

dq[2];              // random access — O(1)
dq.front();         // first element
dq.back();          // last element

// Monotonic deque (sliding window max/min) — see file 08

std::list

Doubly-linked list. O(1) insert/erase anywhere given an iterator; O(n) access and search.

#include <list>

std::list<int> lst = {1, 2, 3, 4};

lst.push_back(5);
lst.push_front(0);
lst.pop_back();
lst.pop_front();

// Splicing — O(1) move of elements between lists
std::list<int> other = {10, 20};
auto it = std::next(lst.begin(), 2); // iterator to 3rd element
lst.splice(it, other);               // insert all of 'other' before 'it'

lst.remove(3);          // remove all elements equal to 3 — O(n)
lst.remove_if([](int x){ return x % 2 == 0; }); // remove evens
lst.reverse();          // reverse in-place — O(n)
lst.sort();             // merge sort — O(n log n)
lst.unique();           // remove consecutive duplicates (sort first)
lst.merge(other);       // merge two sorted lists — O(n)

std::array

Fixed-size array on the stack. Zero overhead vs raw arrays; fully STL-compatible (iterators, algorithms).

#include <array>

std::array<int, 5> a = {1, 2, 3, 4, 5};

a[0];           // unchecked access
a.at(0);        // checked access
a.front();      // first
a.back();       // last
a.size();       // 5 — compile-time constant
a.fill(0);      // set all elements to 0
a.data();       // raw pointer

// Structured binding (C++17)
auto [x1, x2, x3, x4, x5] = a;

// Use in 2D — static allocation, faster than vector<vector>
std::array<std::array<int,4>, 3> mat{};  // 3x4 zero-initialized

std::set and std::multiset

Red-black tree. Sorted, O(log n) all operations. multiset allows duplicate keys.

#include <set>

std::set<int> s = {3, 1, 4, 1, 5};  // {1, 3, 4, 5} — duplicates dropped

s.insert(2);        // O(log n)
s.erase(3);         // erase by value — O(log n)
s.count(4);         // 0 or 1 for set, 0..n for multiset
s.contains(4);      // C++20 — bool
s.find(4);          // iterator or s.end()
s.size();
s.empty();

// Bound tricks — critical for CP
s.lower_bound(x);  // first element >= x
s.upper_bound(x);  // first element > x

// Find largest element <= x
auto it = s.upper_bound(x);
if (it != s.begin()) {
    --it;
    int largest_leq_x = *it;
}

// Find smallest element >= x
auto it2 = s.lower_bound(x);
if (it2 != s.end()) {
    int smallest_geq_x = *it2;
}

// Erase a range
s.erase(s.lower_bound(3), s.upper_bound(7)); // erase all in [3,7]

// Reverse iteration
for (auto it = s.rbegin(); it != s.rend(); ++it) { /* descending */ }

// --- multiset ---
std::multiset<int> ms = {1, 2, 2, 3, 3, 3};

ms.count(3);           // 3
ms.erase(ms.find(2));  // erase exactly ONE occurrence of 2
ms.erase(2);           // erases ALL occurrences of 2 — be careful!

Custom Comparators for set / map

Custom comparators control sort order and equivalence.

// Descending set using greater<>
std::set<int, std::greater<int>> desc_set = {3, 1, 4, 1, 5};
// Iteration: 5 4 3 1

// Lambda comparator (need decltype in template arg)
auto cmp = [](const std::string& a, const std::string& b) {
    return a.size() < b.size() || (a.size() == b.size() && a < b);
};
std::set<std::string, decltype(cmp)> str_set(cmp);
str_set.insert("banana");
str_set.insert("fig");
str_set.insert("apple");  // sorted by length, then lexicographically

// Custom struct comparator — must be strict weak ordering
struct ByCost {
    bool operator()(const std::pair<int,int>& a, const std::pair<int,int>& b) const {
        return a.second < b.second; // sort by second element
    }
};
std::set<std::pair<int,int>, ByCost> cost_set;

// Comparator in sort()
std::sort(v.begin(), v.end(), [](int a, int b){ return a > b; });

std::map and std::multimap

Red-black tree of key-value pairs. Keys are sorted, O(log n) operations.

#include <map>

std::map<std::string, int> freq;

freq["apple"] = 3;                    // insert or update
freq.insert({"banana", 2});           // insert (no-op if key exists)
freq.emplace("cherry", 1);            // construct in-place
freq["date"];                         // inserts key "date" with value 0 if not present!

// Safe access without inserting
if (freq.count("apple"))  { int v = freq["apple"]; }
if (auto it = freq.find("apple"); it != freq.end()) { int v = it->second; }

freq.erase("banana");                 // erase by key
freq.erase(freq.begin());             // erase by iterator

// Iteration — key-value pairs in sorted key order
for (auto& [key, val] : freq) {
    std::cout << key << ": " << val << "\n";
}

// Bound tricks on map
auto lo = freq.lower_bound("b");   // first key >= "b"
auto hi = freq.upper_bound("c");   // first key > "c"

// --- multimap ---
std::multimap<int, std::string> mm;
mm.insert({1, "one"});
mm.insert({1, "uno"});  // duplicate key allowed

// Iterate all values for a key
auto [beg, end] = mm.equal_range(1);
for (auto it = beg; it != end; ++it) {
    std::cout << it->second << "\n";
}

std::unordered_set and std::unordered_map

Hash table. Average O(1) operations; worst case O(n) on hash collisions.

#include <unordered_set>
#include <unordered_map>

std::unordered_set<int> us = {1, 2, 3};
us.insert(4);
us.erase(2);
us.count(3);       // 0 or 1
us.contains(3);    // C++20

std::unordered_map<std::string, int> um;
um["hello"] = 1;
um.count("hello"); // 1
um.erase("hello");

// Bucket API — useful for performance tuning
um.reserve(1000);            // pre-allocate for 1000 elements
um.max_load_factor(0.25);    // lower load factor = fewer collisions
um.bucket_count();           // number of buckets
um.load_factor();            // elements / buckets

// Custom hash for unordered containers
struct PairHash {
    size_t operator()(const std::pair<int,int>& p) const {
        // Combine hashes — shift+XOR is common in CP
        return std::hash<int>{}(p.first) ^ (std::hash<int>{}(p.second) << 32);
    }
};
std::unordered_map<std::pair<int,int>, int, PairHash> pair_map;

Anti-Hack Custom Hash (CP Essential)

unordered_map with the default hash can be hacked in contests by constructing many colliding keys. Use a randomized hash.

#include <chrono>

struct SafeHash {
    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 {
        // Seed with current time to randomize per-run
        static const uint64_t FIXED_RANDOM =
            std::chrono::steady_clock::now().time_since_epoch().count();
        return splitmix64(x + FIXED_RANDOM);
    }
};

// Usage — drop-in replacement for default unordered_map
std::unordered_map<int, int, SafeHash> safe_map;
std::unordered_set<int, SafeHash> safe_set;

std::stack, std::queue, std::priority_queue

Container adaptors built on top of a sequence container.

#include <stack>
#include <queue>

// --- stack (LIFO, backed by deque by default) ---
std::stack<int> stk;
stk.push(1);
stk.push(2);
stk.top();      // 2 — peek without removing
stk.pop();      // remove top (no return value!)
stk.empty();
stk.size();

// Back with vector for better cache performance
std::stack<int, std::vector<int>> vec_stack;

// --- queue (FIFO, backed by deque) ---
std::queue<int> q;
q.push(1);
q.push(2);
q.front();      // 1 — peek front
q.back();       // 2 — peek back
q.pop();        // remove front
q.empty();

// --- priority_queue (max-heap by default) ---
std::priority_queue<int> pq;            // max-heap
pq.push(3);
pq.push(1);
pq.push(4);
pq.top();       // 4 — maximum element
pq.pop();       // remove maximum

// Min-heap — two idioms
std::priority_queue<int, std::vector<int>, std::greater<int>> min_pq;

// Negate values trick for min-heap with default pq
// pq.push(-val) then use -pq.top()

// Custom comparator for pairs (min-heap by first element)
using T = std::pair<int,int>;
std::priority_queue<T, std::vector<T>, std::greater<T>> pair_pq;
pair_pq.push({1, 100});
pair_pq.push({3, 50});
pair_pq.top(); // {1, 100} — smallest first

// Dijkstra-style: {dist, node}
std::priority_queue<std::pair<int,int>,
                    std::vector<std::pair<int,int>>,
                    std::greater<std::pair<int,int>>> dijkstra_pq;

std::bitset

Fixed-size sequence of bits. Extremely fast bit operations via SIMD — can replace boolean arrays and accelerate DP.

#include <bitset>

const int N = 1000;
std::bitset<N> bs;                  // all zeros
std::bitset<N> bs2(0b1011);        // from integer literal
std::bitset<N> bs3("1010011");     // from string (rightmost = bit 0)

bs.set(5);          // set bit 5 to 1
bs.reset(5);        // set bit 5 to 0
bs.flip(5);         // toggle bit 5
bs.flip();          // toggle all bits
bs.set();           // set all bits to 1
bs.reset();         // set all bits to 0

bs[3];              // access bit 3 (unchecked)
bs.test(3);         // access bit 3 (checked — throws if out of range)

bs.count();         // number of set bits (popcount)
bs.any();           // true if any bit is set
bs.none();          // true if no bit is set
bs.all();           // true if all bits are set
bs.size();          // N — number of bits

// Bitwise operations — all O(N/64) using 64-bit words
bs & bs2;           // AND
bs | bs2;           // OR
bs ^ bs2;           // XOR
~bs;                // NOT
bs << 3;            // left shift
bs >> 3;            // right shift

// Convert to/from
bs.to_string();     // "000...1010011"
bs.to_ulong();      // unsigned long (UB if value doesn't fit)
bs.to_ullong();     // unsigned long long

// Bitset DP trick: count paths, subset queries in O(N^2/64)
// Example: dp[i] = bitset of reachable states from i
std::bitset<1001> dp[1001];
// dp[i] |= (dp[j] << cost[j][i]) for transitions

std::string and std::string_view

string owns its character data; string_view is a non-owning read-only window into any character sequence.

#include <string>
#include <string_view>

std::string s = "hello world";

// Access
s[0];               // 'h' — unchecked
s.at(0);            // 'h' — checked
s.front();          // 'h'
s.back();           // 'd'
s.data();           // const char* (null-terminated since C++11)
s.c_str();          // const char* (guaranteed null-terminated)

// Size
s.size();           // 11
s.length();         // 11 — identical to size()
s.empty();          // false

// Modification
s += " there";          // append
s.append("!");          // append
s.push_back('!');       // append single char
s.pop_back();           // remove last char
s.insert(5, ",");       // insert at position
s.erase(5, 1);          // erase 1 char at position 5
s.replace(0, 5, "hi");  // replace range with new string
s.clear();

// Search
s.find("world");        // position or std::string::npos
s.rfind("l");           // last occurrence
s.find_first_of("aeiou"); // first vowel
s.find_last_of("aeiou");  // last vowel
s.find_first_not_of(" \t\n"); // first non-whitespace

// Substrings
s.substr(0, 5);         // "hello" — copies! O(k)
s.compare(other);       // <0, 0, >0

// Conversions
std::to_string(42);         // int -> string
std::stoi("123");           // string -> int
std::stoll("1234567890123"); // string -> long long
std::stod("3.14");          // string -> double

// --- string_view (C++17) — zero-copy read-only reference ---
std::string_view sv = s;            // view into s
std::string_view sv2(s.data(), 5);  // view first 5 chars

sv.substr(0, 5);    // returns string_view — no allocation!
sv.remove_prefix(2); // advance start pointer
sv.remove_suffix(1); // retreat end pointer
sv.starts_with("he"); // C++20
sv.ends_with("lo");   // C++20

// CAUTION: string_view does not own memory — never return a string_view
// to a local string's data (dangling reference)
std::string_view bad() {
    std::string local = "hello";
    return local;  // ← dangling! local is destroyed on return
}

Policy-Based Data Structures (PBDS)

GNU extensions in <ext/pb_ds/> provide an order-statistics tree and a faster hash table.

#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
using namespace __gnu_pbds;

// Order-statistics tree — supports order_of_key and find_by_order
// Equivalent to: set with O(log n) rank and select
typedef tree<int, null_type, std::less<int>,
             rb_tree_tag, tree_order_statistics_node_update>
        ordered_set;

ordered_set os;
os.insert(3);
os.insert(1);
os.insert(4);
os.insert(1); // duplicate — use pair<int,int> to allow duplicates

os.order_of_key(4);    // 2 — number of elements strictly less than 4
*os.find_by_order(0);  // 1 — 0-indexed element by rank
os.find_by_order(os.size()-1); // iterator to maximum element

// Multiset simulation: store {val, unique_id} pairs
ordered_set mos; // type: tree<pair<int,int>, null_type, less<pair<int,int>>, ...>
int id = 0;
// mos.insert({val, id++});
// mos.order_of_key({val, INT_MAX}); // count of elements <= val

// Erase
os.erase(os.find(3)); // erase one element

// Policy-based hash table (faster than unordered_map for integers)
gp_hash_table<int, int> ht;
ht.insert({1, 100});
ht[1]; // access

Container Idioms for CP

Common patterns that appear repeatedly in competitive programming.

// 1. Frequency map
std::map<int, int> freq;
for (int x : arr) freq[x]++;

// 2. Coordinate compression
std::vector<int> vals = arr;             // copy
std::sort(vals.begin(), vals.end());
vals.erase(std::unique(vals.begin(), vals.end()), vals.end()); // remove duplicates
// compress: index of x in vals
auto compress = [&](int x) {
    return (int)(std::lower_bound(vals.begin(), vals.end(), x) - vals.begin());
};

// 3. Sliding window with multiset (maintain sorted window)
std::multiset<int> window;
for (int r = 0; r < n; r++) {
    window.insert(arr[r]);
    if ((int)window.size() > k) window.erase(window.find(arr[r-k]));
    // window.begin() = min, window.rbegin() = max
}

// 4. Two-sum with unordered_set
std::unordered_set<int> seen;
for (int x : arr) {
    if (seen.count(target - x)) { /* found pair */ }
    seen.insert(x);
}

// 5. Stack-based monotonic structure (NGE — Next Greater Element)
std::stack<int> stk;
std::vector<int> nge(n, -1);
for (int i = 0; i < n; i++) {
    while (!stk.empty() && arr[stk.top()] < arr[i]) {
        nge[stk.top()] = arr[i];
        stk.pop();
    }
    stk.push(i);
}

// 6. BFS queue idiom
std::queue<int> bfsq;
std::vector<bool> visited(n, false);
bfsq.push(start);
visited[start] = true;
while (!bfsq.empty()) {
    int u = bfsq.front(); bfsq.pop();
    for (int v : adj[u]) {
        if (!visited[v]) { visited[v] = true; bfsq.push(v); }
    }
}

Quick Reference Table

Container Header Key Operations Notes
vector<T> <vector> push_back, [], size, resize, reserve Best default sequence container
deque<T> <deque> push_front/back, pop_front/back, [] O(1) both ends; slower than vector
list<T> <list> splice, remove_if, sort, merge O(1) insert/erase given iterator
array<T,N> <array> [], at, fill, size (constexpr) Stack-allocated, fixed size
set<T> <set> insert, erase, find, lower/upper_bound RB-tree; unique sorted keys
multiset<T> <set> Same + count, equal_range Duplicate keys allowed
map<K,V> <map> [], insert, find, lower/upper_bound RB-tree; sorted by key
multimap<K,V> <map> insert, find, equal_range Duplicate keys allowed
unordered_set<T> <unordered_set> insert, erase, count, contains Hash table; O(1) avg
unordered_map<K,V> <unordered_map> [], insert, find, count Hash table; O(1) avg; use SafeHash in CP
stack<T> <stack> push, pop, top, empty LIFO adaptor
queue<T> <queue> push, pop, front, back FIFO adaptor
priority_queue<T> <queue> push, pop, top Max-heap; use greater<> for min-heap
bitset<N> <bitset> set, reset, flip, count, any Fixed N at compile time; O(N/64) ops
string <string> find, substr, append, replace, stoi Mutable; owns data
string_view <string_view> substr, remove_prefix, starts_with Non-owning; zero-copy
ordered_set (PBDS) <ext/pb_ds/...> order_of_key, find_by_order GNU only; rank/select in O(log n)