1. C++ Essentials for DSA
You don't need every corner of C++ for this course — you need the handful of features that show up in nearly every solution: references, pass-by-reference, and reading a function signature fluently.
References & pass-by-reference
A reference is an alias for an existing variable — no copy is made. Passing a large
structure like a vector by reference avoids copying it on every call,
which matters constantly once you're passing arrays into recursive functions:
#include <vector>
using namespace std;
// Pass by value: nums is a full COPY -- changes here don't affect the caller's vector
void doubleAllCopy(vector<int> nums) {
for (int& n : nums) n *= 2;
}
// Pass by reference: nums IS the caller's vector -- no copy, changes persist
void doubleAllRef(vector<int>& nums) {
for (int& n : nums) n *= 2;
}
// Pass by const reference: no copy, AND the compiler forbids modifying nums
int sumAll(const vector<int>& nums) {
int total = 0;
for (int n : nums) total += n;
return total;
}
const vector<int>&
When a function reads a vector but doesn't need to modify it, take it as const vector<int>& — you avoid an expensive copy and the compiler catches accidental mutation for you. You'll write this pattern in almost every function signature from Week 2 onward.
Structs for grouping data
A lightweight struct is how you'll bundle related values together —
you'll use exactly this pattern for tree and linked-list nodes starting Week 6:
struct Point {
int x;
int y;
};
Point p = {3, 4};
int distSquared = p.x * p.x + p.y * p.y;
2. Time & Space Complexity
Big-O notation describes how an algorithm's runtime (or memory use)
grows as input size n grows — not the exact number of operations, but
the growth trend once n gets large. It's the language every interviewer
will use to ask "can you do better?"
// O(1) -- constant time, independent of n
int firstElement(const vector<int>& nums) {
return nums[0];
}
// O(n) -- one pass over the input
int sum(const vector<int>& nums) {
int total = 0;
for (int x : nums) total += x; // n iterations
return total;
}
// O(n^2) -- nested loops over the same input
bool hasDuplicate(const vector<int>& nums) {
for (int i = 0; i < nums.size(); i++)
for (int j = i + 1; j < nums.size(); j++) // n iterations, n times
if (nums[i] == nums[j]) return true;
return false;
}
// O(log n) -- halves the search space each step
int binarySearch(const vector<int>& sorted, int target) {
int lo = 0, hi = sorted.size() - 1;
while (lo <= hi) {
int mid = lo + (hi - lo) / 2;
if (sorted[mid] == target) return mid;
else if (sorted[mid] < target) lo = mid + 1;
else hi = mid - 1;
}
return -1;
}
To derive complexity: count how the number of operations scales with n,
then drop constants and lower-order terms. 3n + 100 is O(n);
n² + 500n is O(n²) — the n² term
dominates once n is large enough, regardless of the constant in front of
it.
Space complexity follows the same idea, but for memory: a function
using a fixed number of variables is O(1) space; one that allocates a
new array of size n is O(n) space — and that includes the
memory used by recursion's call stack, which matters once recursive solutions show
up starting Week 8.
Nearly every interview question ends with "what's the time and space complexity of your solution, and can you improve it?" Being able to state and justify Big-O on the spot, without hesitation, is graded as heavily as getting the right answer.
3. STL Essentials: vector, pair & <algorithm>
The C++ Standard Template Library is what makes C++ competitive for interviews
despite its low-level reputation — vector and the algorithm header
cover most of what you'd otherwise hand-write in a lower-level language.
#include <vector>
#include <algorithm>
#include <iostream>
using namespace std;
int main() {
vector<int> nums = {5, 2, 8, 1, 9};
nums.push_back(3); // append -- amortized O(1)
nums.pop_back(); // remove last element
int n = nums.size(); // O(1)
sort(nums.begin(), nums.end()); // O(n log n), ascending
sort(nums.begin(), nums.end(), greater<int>()); // descending
auto it = find(nums.begin(), nums.end(), 8); // O(n) linear search
bool found = (it != nums.end());
int mx = *max_element(nums.begin(), nums.end());
int mn = *min_element(nums.begin(), nums.end());
pair<int, int> p = {1, 2};
cout << p.first << ", " << p.second << "\n";
}
pair<int, int> shows up constantly for bundling two related values —
a coordinate, an (index, value) tuple, or an edge in a graph starting Week 12.
Getting fluent with sort, find, and the *_element
family now means you won't hand-roll them later when a harder problem needs your
full attention elsewhere.
4. Setting Up Your Workflow
You need a C++17-capable compiler and a fast way to compile and run one file at a time — no build system required for this course:
g++ --version
# g++ (...) 11.x or later
g++ -std=c++17 -O2 -o solution solution.cpp
./solution
-std=c++17 enables the C++17 features this course uses (structured
bindings, if with an initializer); -O2 turns on
optimizations, which matters when you're timing a solution against a large test
case. Most learners in this course also keep an account on an online judge
(LeetCode or GeeksforGeeks) open alongside their editor to submit and time solutions
against real constraints.
Before writing code for any problem, write down its expected time/space complexity target based on the input constraints given (e.g., "n up to 10^5 rules out O(n²)"). This one habit, started in Week 1, is what separates fast problem-solving from guessing later in the course.
5. Hands-on Exercise
Implement and compare a duplicate-finder in three ways
Apply this week's C++, STL and complexity skills to see the same problem get faster as its complexity improves.
Requirements:
- Set up your compiler and confirm
g++ -std=c++17 -O2 -o solution solution.cpp && ./solutionruns. - Write
bool hasDuplicateBruteForce(const vector<int>& nums)using nested loops, and state its time complexity in a comment. - Write
bool hasDuplicateSorted(vector<int> nums)that sorts a copy withstd::sortand checks adjacent elements — state its time and space complexity. - Write
bool hasDuplicateSet(const vector<int>& nums)using anunordered_set<int>, achieving O(n) time. - Generate a vector of 100,000 random integers, time all three with
<chrono>, and print the results side by side.
Use chrono::high_resolution_clock::now() before and after each call, and print the difference in milliseconds. Seeing the O(n²) version take noticeably longer than O(n log n), which in turn takes longer than O(n), makes Big-O concrete instead of theoretical.
6. Knowledge Check
Four quick questions. Expand each to check your answer.
Q1
Why prefer void f(const vector<int>& nums) over void f(vector<int> nums) when the function only reads nums?
Why prefer void f(const vector<int>& nums) over void f(vector<int> nums) when the function only reads nums?
Passing by value copies the entire vector — O(n) time and space just to call the function — while passing by const reference passes an alias with no copy at all. The const also lets the compiler reject any accidental attempt to modify the caller's data inside the function, catching a class of bugs at compile time.
Q2
What's the time complexity of checking every pair in an n-element array with two nested loops, and why?
What's the time complexity of checking every pair in an n-element array with two nested loops, and why?
O(n²) — the outer loop runs n times, and for each of those, the inner loop runs up to n times, giving roughly n × n total operations. Big-O drops the constant factor and keeps only the dominant growth term, so this is classified as O(n²) regardless of the exact iteration counts.
Q3
Why does binary search require the input to already be sorted?
Why does binary search require the input to already be sorted?
Binary search decides which half of the remaining range to discard by comparing the middle element to the target — that decision is only valid if every element to one side is guaranteed to be smaller (or larger) than the middle. On unsorted data that guarantee doesn't hold, so discarding half the array could throw away the answer.
Q4
Between the sorted-array approach and the unordered_set approach to finding a duplicate, which is faster, and what does it cost you?
Between the sorted-array approach and the unordered_set approach to finding a duplicate, which is faster, and what does it cost you?
The unordered_set approach is faster asymptotically — O(n) time versus the sorted approach's O(n log n) — because it inserts each element once and checks membership in expected O(1) time rather than paying a sorting cost up front. The trade-off is space: the set uses O(n) extra memory, while sorting a copy also uses O(n) extra memory but sorting in place would use only O(1) extra space at the cost of mutating the input.