Week 1: C++ & Complexity Foundations

Every module after this one assumes two things: that you can read and write basic C++ without stumbling over references and pointers, and that you can look at a piece of code and say how its runtime grows as input size grows. This week builds both from scratch — C++ syntax essentials, Big-O analysis, and the handful of STL tools you'll use in almost every solution for the rest of this course.

Module 1 of 12 Week 1 of 15 ~3–4 Hours Hands-on Exercise Included

By the end of this week, you'll be able to

  • Write C++ functions using references and pass-by-reference correctly
  • Derive the time and space complexity of a piece of code and state it in Big-O
  • Use vector, pair and core <algorithm> functions fluently

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:

references.cpp
#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;
}
Default to 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:

point.cpp
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?"

complexity examples
// 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 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.

Why this matters in interviews

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.

stl_basics.cpp
#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:

terminal
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.

Build the habit now

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

Hands-on

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:

  1. Set up your compiler and confirm g++ -std=c++17 -O2 -o solution solution.cpp && ./solution runs.
  2. Write bool hasDuplicateBruteForce(const vector<int>& nums) using nested loops, and state its time complexity in a comment.
  3. Write bool hasDuplicateSorted(vector<int> nums) that sorts a copy with std::sort and checks adjacent elements — state its time and space complexity.
  4. Write bool hasDuplicateSet(const vector<int>& nums) using an unordered_set<int>, achieving O(n) time.
  5. Generate a vector of 100,000 random integers, time all three with <chrono>, and print the results side by side.
Hint

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?

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?

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?

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?

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.

← Back to Full Syllabus Up next Week 2: Arrays, Two Pointers & Sliding Window — coming soon