CMD Guide
HomeDSA

Foundations

Step 1 in the DSA path · 47 concepts · 0 problems

0 / 47 complete

📘 Learn Foundations from zero

The problem: two algorithms both solve a task — which is "better"? Timing them on your laptop is misleading: faster hardware, a different language, or a lucky input all change the number. We need a measure that depends only on the algorithm and the size of the input, written n.

Analogy: imagine planning a road trip. You don't care that one route is "5 minutes faster on a Tuesday." You care how each route scales: does travel time grow with the number of cities linearly, or does it explode (visit-every-pair)? Asymptotic analysis is exactly this — how does the work grow as the input grows toward infinity? Constants (your car's speed) and small detours (lower-order terms) wash out; the growth rate is what survives.

Worked example: find the max in an array of n numbers. You look at each element once, keeping a running best. For n elements you do roughly n comparisons. If n doubles, the work doubles — that is linear time, O(n). Now count duplicate pairs by comparing every element to every other: for each of n elements you scan n others ≈ operations. Double n and the work quadruplesO(n²). We write 3n + 7 as just O(n): the 3 and the +7 are irrelevant once n is large, because the n term dominates.

Key insight: asymptotic notation describes how an algorithm's cost grows, not how long it takes. Big-O bounds it from above; Θ pins the exact growth rate when upper and lower bounds match. Either way you keep only the dominant term and drop constants, so you can compare algorithms independent of machine, language, or input luck.

✨ Added by the guide to build intuition — not from the source course.

🎯 Guided practice

Problem 1 (easy): What is the time complexity?

  1. Code: for i in range(n): print(i) then separately for j in range(n): print(j).
  2. Count each loop: the first runs n times, the second runs n times. Sequential blocks add: n + n = 2n.
  3. Drop the constant factor 2O(n). Lesson: sequential code sums; constants vanish.
  4. Contrast: if the second loop were nested inside the first, you'd multiplyn × n = O(n²). Nesting multiplies, sequencing adds.

Problem 2 (medium): Solve the recurrence for binary search and for merge sort.

  1. Binary search recurrence: each call discards half the array and recurses on one half, doing O(1) work to pick the middle → T(n) = T(n/2) + O(1).
  2. Recursion-tree view: each level does constant work, and you halve until size 1, so there are log₂ n levels → O(log n).
  3. Merge sort: split in half, recurse on both halves, then merge in linear time → T(n) = 2T(n/2) + O(n).
  4. Apply the Master Theorem with a=2, b=2, f(n)=n. Compute the watershed n^(log_b a) = n^(log₂ 2) = n¹ = n. Since f(n)=n = Θ(n^(log_b a)) — they match up to a log^k n factor with k=0 — this is Case 2, giving Θ(n^(log_b a) · log^(k+1) n)Θ(n log n).
  5. Pattern (the three cases): when you split into a subproblems of size n/b plus f(n) combine work, compare f(n) against the watershed n^(log_b a). If the watershed wins → Θ(watershed) (Case 1); a tie → ×log n (Case 2); if f(n) wins polynomially and satisfies the regularity condition → Θ(f(n)) (Case 3).

✨ Added by the guide — work these before the full problem set.

Lessons in this topic

🧠 Review & recall

Active recall is what moves a topic into long-term memory. Flip each card before revealing, then test yourself — your results are saved on this device.

Flashcard
What does asymptotic (Big-O) analysis actually measure, and why not just time the algorithm on your laptop?
tap to reveal →
It measures how an algorithm's cost grows as input size n grows toward infinity, keeping only the dominant term and dropping constants and lower-order terms (e.g. 3n+7 becomes O(n)). Wall-clock timing is misleading because faster hardware, a different language, or a lucky input change the number.
💡 Road-trip rule: you care how travel time SCALES with cities, not '5 min faster on a Tuesday.'
Flashcard
How do you combine complexities for nested loops, consecutive (sequential) loops, and a single conditional?
tap to reveal →
Nested loops multiply (for i in n, for j in n -> n*n = O(n^2)). Consecutive/sequential loops add (n + m, or n + n = 2n -> O(n)). A single conditional is constant O(1) because exactly one branch runs regardless of n.
💡 Nesting multiplies, sequencing adds; an if is just O(1).
Flashcard
What is the formal definition of Big-O, f(n) = O(g(n))?
tap to reveal →
f(n) = O(g(n)) if there exist a positive constant C and a threshold n0 such that f(n) <= C*g(n) for all n >= n0. It is an upper bound: f does not grow faster than a constant multiple of g for large n.
💡 Find a C and an n0 — after n0, C*g(n) sits on top forever.
Flashcard
State insertion sort's best, average, and worst case time complexities and what input triggers each.
tap to reveal →
Best case is O(n) on an already-sorted list (inner while loop exits after one comparison per element). Average case on a randomly ordered list is O(n^2) (each element compared with about half the sorted portion). Worst case on a reverse-sorted list is O(n^2) (0+1+2+...+(n-1) comparisons/shifts).
💡 Sorted = O(n) free ride; reversed = O(n^2) every-element-shifts.
Flashcard
For a recurrence T(n)=aT(n/b)+f(n), how does the Master Theorem pick a case using the watershed n^(log_b a)?
tap to reveal →
Compare f(n) to the watershed n^(log_b a). Case 1: f grows slower -> Theta(n^(log_b a)) (leaves dominate). Case 2: f matches it -> multiply by log n -> Theta(n^(log_b a) log n). Case 3: f grows faster (with regularity condition) -> Theta(f(n)) (root dominates). Merge sort (a=2,b=2,f=n) gives watershed n^1=n, a tie -> Case 2 -> Theta(n log n).
💡 Watershed wins->Case1; tie->xlogn (Case2); f wins->Theta(f) (Case3).
Flashcard
Average vs worst-case time complexity of hash table / hash set operations (insert, search, delete), and their space?
tap to reveal →
Insertion, deletion, and search (contains) are O(1) on average thanks to hashing, but degrade to O(n) in the worst case when collisions pile elements into one bucket/list. Space complexity is O(n) for the n stored entries.
💡 Hash = O(1) on average, O(n) when collisions stack a bucket; O(n) space.
Q1. A function runs `for i in range(n): print(i)` then SEPARATELY `for j in range(n): print(j)`. What is its time complexity?
Q2. Applying the Master Theorem to merge sort, T(n) = 2T(n/2) + O(n), which case applies and what is the result?
Q3. Which statement about Big-O notation is correct per the lessons?
Q4. A recursive function `largest_power_of_two(n)` returns `1 + f(n/2)` until n <= 1. What are its time and space complexities?
Q5. The recursive 'find all subsets' function makes two recursive calls per element (include / exclude), giving T(n)=2T(n-1)+O(1). Why is it infeasible for large n?