Source notes
- C++ working draft - Library introductionNormative vocabulary and requirements for C++ standard-library components; the draft is a technical reference, not the declared compiler version.
- C++ working draft - Sorting and related operationsComparator requirements, ordering algorithms, lower_bound, and strict weak ordering.
- cppreference - Containers libraryReference summaries for sequence, associative, unordered, and adaptor containers, including invalidation and complexity tables.
- cppreference - std::basic_stringString storage, access, mutation, comparison, and operation-specific complexity references.
- cppreference - std::numeric_limitsPortable integer range inspection used to state arithmetic boundaries without assuming one machine width beyond the fixed guarantees.
Input and output conventions#
- Function interview
- Implement the requested function against the provided signature. Clarify whether inputs may be mutated, pass large read-only containers by const reference, and return a value instead of adding stdin/stdout plumbing unless asked.
- Online assessment
- Parse exactly the documented format with std::cin, write only the requested result with std::cout, keep synchronization settings consistent, and test multi-case, empty, and trailing-whitespace behavior permitted by the statement.
Quick Reference#
The syntax candidates most often want immediately before a coding round.
long long rangeSum(const std::vector<int>& nums) {
long long total = 0;
for (int value : nums) total += value;
return total;
}
void sortInPlace(std::vector<int>& nums) {
std::sort(nums.begin(), nums.end());
}
std::vector<int> sortedCopy(std::vector<int> nums) {
std::sort(nums.begin(), nums.end());
return nums;
}const& borrows read-only data, & permits caller-visible mutation, and pass-by-value deliberately makes a copy that can be moved on return.
std::vector<int> nums(n, 0);
nums.push_back(value);
int last = nums.back();
nums.pop_back();
std::string text = "code";
char first = text[0];
std::string suffix = text.substr(1);
text += '!';std::unordered_map<int, int> frequency;
std::unordered_set<int> seen;
++frequency[value];
seen.insert(value);
if (auto it = frequency.find(target); it != frequency.end()) {
int count = it->second;
}
std::map<int, int> orderedCounts;
std::set<int> orderedValues;std::queue<int> queue;
queue.push(start);
int next = queue.front();
queue.pop();
std::stack<int> stack;
stack.push(value);
int top = stack.top();
stack.pop();
std::priority_queue<int> maxHeap;
std::priority_queue<int, std::vector<int>, std::greater<int>> minHeap;std::sort(nums.begin(), nums.end());
auto firstAtLeast = std::lower_bound(nums.begin(), nums.end(), target);
std::size_t index = static_cast<std::size_t>(firstAtLeast - nums.begin());
std::sort(items.begin(), items.end(), [](const Item& a, const Item& b) {
if (a.score != b.score) return a.score > b.score;
return a.id < b.id;
});A comparator returns whether a must appear before b. Equal keys must compare false in both directions.
C++17 and Interview I/O#
Treat the supplied signature and compiler mode as part of the problem contract. Most live interviews want a function; most online assessments want exact parsing and output around the same algorithm.
std::vector<int> doubled(const std::vector<int>& values) {
std::vector<int> answer;
answer.reserve(values.size());
for (int value : values) answer.push_back(value * 2);
return answer;
}The input is borrowed and cannot be mutated through this reference.
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
if (!(std::cin >> n)) return 0;
std::vector<int> values(n);
for (int& value : values) std::cin >> value;
std::cout << rangeSum(values) << '
';
}Do not print prompts or diagnostics unless the assessment explicitly requests them.
- Prefer individual standard headers in submitted code; <bits/stdc++.h> is not standard C++.
- Confirm whether the judge invokes a function, constructs a class, or runs main before choosing an I/O shell.
- Do not mix stdio and iostream calls after disabling synchronization unless you deliberately manage their interaction.
vector and string#
std::vector is the default dynamic contiguous sequence; std::string is a mutable byte/code-unit sequence. Both own their elements and may reallocate as they grow.
std::vector<int> values = {3, 1, 4};
values.reserve(8);
values.push_back(1);
values.emplace_back(5);
for (std::size_t i = 0; i < values.size(); ++i) {
values[i] += 1;
}
std::vector<int> middle(values.begin() + 1, values.begin() + 3);std::string text = "level";
bool same = text == "level";
std::reverse(text.begin(), text.end());
std::string part = text.substr(1, 3);
for (unsigned char byte : text) {
if (std::isdigit(byte)) {
// Classification is safe for this converted byte value.
}
}Indexing std::string visits code units/bytes, not user-perceived Unicode characters.
- vector::at performs bounds checking and operator[] does not.
- push_back may reallocate, invalidating every iterator, pointer, and reference into the vector.
- substr returns a new string and copies the requested characters; repeated slicing can hide quadratic work.
- std::string equality is content equality. const char* equality with == compares pointer addresses, not text.
| Operation | Complexity | Interview note |
|---|---|---|
vector[i] / string[i] | O(1) | No bounds check for operator[]. |
vector.push_back(x) | O(1) amortized | A reallocation step itself is linear and invalidates references/iterators. |
vector.insert / erase in middle | O(n) | Following elements move. |
string.substr(pos, count) | O(k) | k characters are copied into a new string. |
vector/string comparison | O(min(a,b)) | Lexicographic comparison may stop at the first difference. |
unordered_map / unordered_set and map / set#
Choose unordered containers for average constant-time key access and ordered containers when sorted iteration or logarithmic boundary operations are part of the invariant.
std::unordered_map<int, int> frequency;
for (int value : nums) ++frequency[value];
auto it = frequency.find(target);
if (it != frequency.end()) {
int count = it->second;
}
std::map<int, int> ordered;
auto firstNotLess = ordered.lower_bound(target);operator[] inserts a missing key with a value-initialized mapped value. Use find or contains-like logic when a read must not mutate; C++17 has no unordered_map::contains.
- unordered_map and unordered_set iteration order is unspecified; never use it as a tie rule.
- Custom unordered keys need equality and a hash consistent with that equality.
- map and set order keys with a comparator that must satisfy strict weak ordering.
- Rehashing an unordered container invalidates iterators but not references or pointers to its elements; erase invalidates the erased element.
| Operation | Complexity | Interview note |
|---|---|---|
unordered_map find/insert/erase | O(1) average; O(n) worst case | Hash quality, equality, load factor, and adversarial collisions matter. |
map find/insert/erase | O(log n) | Ordered by its comparator. |
unordered_set membership | O(1) average; O(n) worst case | Not available |
set membership/lower_bound | O(log n) | Not available |
deque, queue, and stack#
std::queue and std::stack are container adaptors with intentionally small APIs. Use std::deque directly when the algorithm genuinely needs both ends or indexed blocks.
std::queue<int> pending;
pending.push(start);
while (!pending.empty()) {
int node = pending.front();
pending.pop();
}
std::stack<int> path;
path.push(start);
int node = path.top();
path.pop();
std::deque<int> candidates;
candidates.push_back(index);
candidates.pop_front();| Operation | Complexity | Interview note |
|---|---|---|
queue push/pop/front | O(1) | With the default deque backing. |
stack push/pop/top | O(1) | With the default deque backing. |
deque push/pop at either end | O(1) | Not available |
deque insert/erase in middle | O(n) | Not available |
priority_queue#
std::priority_queue is a max-heap by default. Supply std::greater or a custom comparator for a min-heap and make every tie rule explicit when output order is observable.
using Entry = std::pair<long long, int>;
std::priority_queue<Entry, std::vector<Entry>, std::greater<Entry>> minHeap;
minHeap.push({distance, node});
auto [bestDistance, bestNode] = minHeap.top();
minHeap.pop();std::pair compares lexicographically, so node breaks equal-distance ties.
struct EarlierDeadline {
bool operator()(const Task& a, const Task& b) const {
if (a.deadline != b.deadline) return a.deadline > b.deadline;
return a.sequence > b.sequence;
}
};
std::priority_queue<Task, std::vector<Task>, EarlierDeadline> tasks;For priority_queue, returning true means a has lower priority and should appear after b.
| Operation | Complexity | Interview note |
|---|---|---|
priority_queue.top() | O(1) | Not available |
priority_queue.push/pop | O(log n) | Not available |
construct from a range | O(n) | Heap construction differs from n separate pushes. |
sort, lower_bound, and comparison#
Sorting exposes adjacency and greedy order; lower_bound finds the first position whose value is not less than a target. Both rely on an ordering relation that stays consistent for the entire operation.
std::sort(intervals.begin(), intervals.end(),
[](const Interval& a, const Interval& b) {
if (a.start != b.start) return a.start < b.start;
return a.end < b.end;
});auto it = std::lower_bound(nums.begin(), nums.end(), target);
std::size_t libraryIndex = static_cast<std::size_t>(it - nums.begin());
std::size_t lowerBound(const std::vector<int>& nums, int target) {
std::size_t left = 0;
std::size_t right = nums.size();
while (left < right) {
std::size_t mid = left + (right - left) / 2;
if (nums[mid] < target) left = mid + 1;
else right = mid;
}
return left;
}| Operation | Complexity | Interview note |
|---|---|---|
sort(first,last) | O(n log n) comparisons | Comparator cost multiplies the comparison count. |
lower_bound on random-access range | O(log n) comparisons | The range must already be partitioned under the same ordering. |
stable_sort | O(n log n) comparisons when additional memory is available | Choose stability only when equal-key input order is semantically required. |
References, copies, mutation, and iterator lifetime#
C++ makes ownership choices visible in function signatures. Say whether you borrow, mutate, copy, or move data, and do not keep iterators or references across operations that invalidate them.
int firstValue(const std::vector<int>& nums); // borrow read-only
void normalize(std::vector<int>& nums); // mutate caller data
std::vector<int> sorted(std::vector<int> nums); // own a copy
for (const Item& item : items) {
inspect(item); // no per-item copy
}
for (Item& item : items) {
item.visited = false; // caller-visible mutation
}for (auto it = values.begin(); it != values.end();) {
if (*it < 0) it = values.erase(it);
else ++it;
}Use the iterator returned by erase. A range-for loop cannot safely keep its hidden iterator after vector erasure.
- A const reference extends the lifetime of a temporary for that reference's scope, but returning a reference to local storage is always dangling.
- Copying a large vector in a recursive call can change both complexity and correctness expectations.
- vector growth and insert/erase can invalidate positions. Reacquire iterators and references after the mutating operation.
- Use smart pointers only when the problem actually asks you to own dynamically allocated nodes; ordinary interview traversals usually borrow provided nodes.
Integers, text, and equality assumptions#
State numeric range, character model, and equality semantics before they become hidden bugs. C++ signed overflow is undefined behavior, not a portable wraparound rule.
long long product = 1LL * width * height;
long long total = 0;
for (int value : nums) total += value;
if (count > std::numeric_limits<long long>::max() / factor) {
throw std::overflow_error("product exceeds long long");
}
long long checkedProduct = count * factor;std::string first = "node";
std::string second = "node";
bool sameText = first == second; // true: content
const char* left = first.c_str();
const char* right = second.c_str();
bool sameAddress = left == right; // pointer identity, not text equality- int multiplication happens in int even when assigned to long long; write 1LL * a * b or cast an operand first.
- Unsigned arithmetic wraps modulo 2^N, but mixing signed and unsigned values can make a negative index look enormous.
- std::string stores char code units. UTF-8 user-perceived characters may occupy several bytes, so clarify whether the task is byte-, code-point-, or grapheme-based.
- std::vector, std::string, std::pair, and standard associative containers define value/content equality when their element/key equality is defined.
Operation Complexity Reference#
Name the specific container and operation. Do not transfer a complexity claim from a similarly named structure in another language or omit hashing, comparison, copying, and output costs.
| Operation | Complexity | Interview note |
|---|---|---|
vector random access | O(1) | Not available |
vector push_back | O(1) amortized | Individual reallocation can be O(n). |
deque push/pop at ends | O(1) | Not available |
unordered_map/set lookup | O(1) average; O(n) worst case | State the average-case hashing assumption. |
map/set lookup | O(log n) | Not available |
priority_queue push/pop | O(log n) | Not available |
sort | O(n log n) comparisons | Include nonconstant comparator work when applicable. |
lower_bound on vector | O(log n) comparisons | Not available |
string append | Amortized relative to growth | Appending k characters must process/copy those characters. |
copy vector/string of length n | O(n) | A by-value parameter can make this cost visible. |
Common Interview Templates#
These are structural recall aids, not answers to proprietary problems. Open only the pattern you need.
Frequency MapCounts or membership replace repeated scans over values already seen.
Use when: Counts or membership replace repeated scans over values already seen.
Typical complexity: O(n) expected time and O(u) space for u distinct values; worst-case hashing is linear per operation.
std::unordered_map<int, int> frequencies(const std::vector<int>& nums) {
std::unordered_map<int, int> count;
count.reserve(nums.size());
for (int value : nums) ++count[value];
return count;
}Queue BFSYou need reachability, components, or shortest edge count in an unweighted graph.
Use when: You need reachability, components, or shortest edge count in an unweighted graph.
Typical complexity: O(V + E) time and O(V) auxiliary space for an adjacency-list graph.
std::vector<int> bfs(const std::vector<std::vector<int>>& graph, int start) {
std::vector<int> order;
std::vector<bool> seen(graph.size(), false);
std::queue<int> pending;
seen[start] = true;
pending.push(start);
while (!pending.empty()) {
int node = pending.front();
pending.pop();
order.push_back(node);
for (int next : graph[node]) {
if (!seen[next]) {
seen[next] = true;
pending.push(next);
}
}
}
return order;
}Iterative DFSYou need depth-first reachability without relying on the process call-stack depth.
Use when: You need depth-first reachability without relying on the process call-stack depth.
Typical complexity: O(V + E) time and O(V) auxiliary space.
std::vector<int> iterativeDfs(const std::vector<std::vector<int>>& graph, int start) {
std::vector<int> order;
std::vector<bool> seen(graph.size(), false);
std::stack<int> pending;
pending.push(start);
while (!pending.empty()) {
int node = pending.top();
pending.pop();
if (seen[node]) continue;
seen[node] = true;
order.push_back(node);
for (auto it = graph[node].rbegin(); it != graph[node].rend(); ++it) {
if (!seen[*it]) pending.push(*it);
}
}
return order;
}Lower BoundA sorted sequence requires the first index whose value is at least the target.
Use when: A sorted sequence requires the first index whose value is at least the target.
Typical complexity: O(log n) time and O(1) auxiliary space for vector random access.
std::size_t lowerBound(const std::vector<int>& nums, int target) {
std::size_t left = 0;
std::size_t right = nums.size();
while (left < right) {
std::size_t mid = left + (right - left) / 2;
if (nums[mid] < target) left = mid + 1;
else right = mid;
}
return left;
}Size-k HeapYou need the k largest values without sorting the entire input.
Use when: You need the k largest values without sorting the entire input.
Typical complexity: O(n log k) time and O(k) auxiliary space.
std::vector<int> largestK(const std::vector<int>& nums, std::size_t k) {
std::priority_queue<int, std::vector<int>, std::greater<int>> kept;
for (int value : nums) {
kept.push(value);
if (kept.size() > k) kept.pop();
}
std::vector<int> answer;
while (!kept.empty()) {
answer.push_back(kept.top());
kept.pop();
}
return answer; // ascending among the retained k values
}Union-FindEdges repeatedly merge components and connectivity queries interleave with those merges.
Use when: Edges repeatedly merge components and connectivity queries interleave with those merges.
Typical complexity: O((n + operations) α(n)) time and O(n) space with compression and union by size.
class UnionFind {
public:
explicit UnionFind(int n) : parent(n), size(n, 1) {
std::iota(parent.begin(), parent.end(), 0);
}
int find(int node) {
while (node != parent[node]) {
parent[node] = parent[parent[node]];
node = parent[node];
}
return node;
}
bool unite(int a, int b) {
a = find(a);
b = find(b);
if (a == b) return false;
if (size[a] < size[b]) std::swap(a, b);
parent[b] = a;
size[a] += size[b];
return true;
}
private:
std::vector<int> parent;
std::vector<int> size;
};BacktrackingYou must enumerate a choice tree and restore shared state after every branch.
Use when: You must enumerate a choice tree and restore shared state after every branch.
Typical complexity: O(2^n · n) including copied subset output, with O(n) path/call-stack space excluding output.
void buildSubsets(const std::vector<int>& nums, std::size_t index,
std::vector<int>& path,
std::vector<std::vector<int>>& answer) {
if (index == nums.size()) {
answer.push_back(path);
return;
}
buildSubsets(nums, index + 1, path, answer);
path.push_back(nums[index]);
buildSubsets(nums, index + 1, path, answer);
path.pop_back();
}1-D Dynamic ProgrammingEach prefix state depends on a fixed number of earlier prefix states.
Use when: Each prefix state depends on a fixed number of earlier prefix states.
Typical complexity: O(n) time and O(1) auxiliary space.
long long bestNonAdjacentTotal(const std::vector<int>& values) {
long long throughPreviousTwo = 0;
long long throughPreviousOne = 0;
for (int value : values) {
long long current = std::max(throughPreviousOne,
throughPreviousTwo + value);
throughPreviousTwo = throughPreviousOne;
throughPreviousOne = current;
}
return throughPreviousOne;
}Common Interview Mistakes#
Copying large inputs
A by-value vector parameter performs O(n) work unless ownership/copying is intentional. Prefer const& for read-only borrowing.
Keeping invalidated positions
vector growth and erase can invalidate iterators, pointers, and references. Reacquire them after mutation.
Non-strict comparator
Using <= or >= violates strict weak ordering for equal values and breaks algorithm preconditions.
Wrong heap direction
priority_queue is a max-heap by default; size-k-largest commonly needs a min-heap so the weakest retained candidate is removable.
Read with operator[]
map[key] and unordered_map[key] insert a missing key. Use find when absence must not mutate state.
Assuming hash order
unordered container iteration order is unspecified and cannot serve as an output or tie contract.
Late numeric promotion
Casting or assigning after int arithmetic cannot repair overflow that already occurred. Promote an operand first.
Mixed signed and unsigned indices
size() is unsigned; subtracting or comparing a negative int can convert it to a large unsigned value.
Byte equals character
std::string indexing accesses code units. UTF-8 characters can occupy multiple bytes.
Using non-standard convenience headers
<bits/stdc++.h> may compile locally but is not part of C++17 and may be rejected by another toolchain.
Broken and corrected examples#
Each pair names the failing case that the language fixture must catch.
Non-strict sorting comparator
Failure: For equal values the broken comparator says each value precedes the other. It violates strict weak ordering, so std::sort's required precondition is not met and behavior is undefined.
Regression case: Compile both with C++17; run on {2,1,2,1}. Assert the fixed result is {1,1,2,2} and explicitly check comp(2,2)==false.
std::sort(values.begin(), values.end(),
[](int a, int b) { return a <= b; });std::sort(values.begin(), values.end(),
[](int a, int b) { return a < b; });Reference kept across vector growth
Failure: push_back may reallocate the vector and invalidate the stored reference. Writing through an invalidated reference is undefined behavior.
Regression case: Compile with C++17 and sanitizers where available; force growth from capacity 1, then assert the fixed vector is {8,9}. The correctness claim relies on invalidation rules, not on a crash occurring.
std::vector<int> values = {7};
int& first = values.front();
values.push_back(9); // may reallocate
first = 8; // possibly dangling: undefined behaviorstd::vector<int> values = {7};
values.push_back(9);
values.front() = 8; // reacquire after growthErasing from vector during range iteration
Failure: vector::erase invalidates the erased position and all following iterators, including the range-for loop's hidden iterator.
Regression case: Compile with C++17; run on {-1,-2,3,-4}. Assert the fixed result is {3}, including consecutive erased elements.
for (int value : values) {
if (value < 0) {
values.erase(std::find(values.begin(), values.end(), value));
}
}for (auto it = values.begin(); it != values.end();) {
if (*it < 0) it = values.erase(it);
else ++it;
}Promotion after signed overflow
Failure: Both broken operands are int, so multiplication occurs in int before assignment. If it exceeds the int range, signed overflow is undefined behavior.
Regression case: Compile with C++17; assert the fixed result equals 1'000'000'000'000LL. With an undefined-behavior sanitizer, the broken expression must be diagnosed rather than treated as portable wraparound.
int width = 1'000'000;
int height = 1'000'000;
long long area = width * height;int width = 1'000'000;
int height = 1'000'000;
long long area = 1LL * width * height;Language transfer exercises#
Use these to check runtime judgment before another algorithm repetition. Open the answer check only after committing to a result.
- predict
Vector lifetime after growth
A reference aliases values[0], then push_back grows a full vector. Is reading through the old reference valid, and does reserve(10) change the answer when size remains below 10?
Answer check
Growth that reallocates invalidates the reference. Reserving enough capacity prevents reallocation for pushes within that capacity, but code should still document why the lifetime is safe.
- trace
Trace a min-heap tie
Trace a priority_queue of pair<int,int> using greater with entries (4,2), (4,1), and (3,9). List pop order.
Answer check
Lexicographic min order is (3,9), (4,1), (4,2). The second pair field is the deterministic tie key.
- repair
Repair overflow and copying
Repair long long total = accumulate(nums.begin(), nums.end(), 0) and a helper taking vector<int> by value when it only reads.
Answer check
Use std::accumulate(..., 0LL) so accumulation is long long, and take const std::vector<int>& to avoid the unintended O(n) copy.
- choose
Choose the STL structure
Choose vector, unordered_set, map, queue, or priority_queue for indexed storage, expected-fast membership, sorted key traversal, FIFO discovery, and repeated next-priority access.
Answer check
vector, unordered_set, map, queue, and priority_queue respectively; state the average-case hashing assumption and heap direction.
- transfer
Unlabeled boundary transfer
Given sorted event times with duplicates, return the index of the first time not earlier than a query. Name both the library call and the canonical concept page.
Answer check
Use std::lower_bound and subtract begin() for the index; the canonical handoff is Binary Search at /dsa/roadmap/topic-map?topic=binary-search.