Lecture W1M2: Introduction to the Analysis of Algorithms
August 24, 2026
By the end of this session, you will be able to:
The goal of this course is to teach you to solve computational problems, and to communicate that your solutions are correct and efficient.
Half of your grade in life as a computer scientist is the second part: convincing another human. Proofs and analysis are how algorithm designers communicate.
A computational problem is a binary relation from problem inputs to correct outputs. Because listing every correct output for every input is usually impossible, we instead give a verifiable predicate, a property that any correct output must satisfy.
This course studies problems over large, general input spaces: the problem must be stated for inputs of arbitrary size n, not for one fixed situation.
An instance: In this room, right now, is there a pair of students who share a birthday?
The general problem: Given any set of n students, is there a pair with the same birthday?
Two observations:
An algorithm is a deterministic procedure mapping each input to a single output. An algorithm solves a problem if it returns a correct output for every input of the problem.
Your neighbor’s checkout-line strategy from the activity is a procedure. Whether it solves the fastest-line problem, for every store on every day, is exactly the kind of claim this course teaches you to interrogate.
Problem: Given n students, return a pair with the same birthday, or None if none exists.
Algorithm (interview method):
Simple. But is it correct? And is it fast? Those are always the two questions.
“I ran it on ten test cases and it worked” is evidence, not proof. Tests can show the presence of bugs, never their absence.
Induction hypothesis: if the first k students contain a matching pair, the algorithm returns a match before interviewing student k + 1.
Either way the hypothesis holds for k’ + 1, completing the induction.
Wall-clock time is a property of the machine, not the algorithm. We want machine-independent analysis:
We ignore constant factors and lower-order terms and keep the growth rate:
How growth rates feel at \(n = 1000\), at one operation per nanosecond:
| Growth | Count at \(n = 1000\) | Rough time |
|---|---|---|
| constant \(\Theta(1)\) | 1 | 1 ns |
| logarithmic \(\Theta(\log n)\) | about 10 | 10 ns |
| linear \(\Theta(n)\) | 1000 | 1 microsecond |
| log-linear \(\Theta(n \log n)\) | about 10,000 | 10 microseconds |
| quadratic \(\Theta(n^2)\) | 1,000,000 | 1 millisecond |
| exponential \(2^{\Theta(n)}\) | about \(10^{301}\) | longer than the universe |
Think of estimating how long a trip takes:
For algorithms:
A useful memory aid:
\[\boxed{O = \text{ceiling}, \quad \Omega = \text{floor}, \quad \Theta = \text{both}}\]
Counting operations only means something if we agree on what the machine can do in constant time. Our model is the word-RAM:
Python is a much fancier model, but it is implemented on top of a word-RAM, and we will keep track of what its conveniences really cost.
A data structure is a way to store non-constant data supporting a set of operations; the set of operations is called its interface.
Two interfaces organize the first third of the course:
Example: a static array supports build in \(\Theta(n)\) and get_at / set_at in \(\Theta(1)\). Different structures implement the same interface with very different performance, and that difference is the whole game.
def birthday_match(students):
"""students: tuple of (name, bday) tuples.
Returns a matching pair of names, or None."""
n = len(students) # O(1)
record = StaticArray(n) # O(n)
for k in range(n): # n rounds
(name1, bday1) = students[k] # O(1)
for i in range(k): # k rounds
(name2, bday2) = record.get_at(i) # O(1)
if bday1 == bday2: # O(1)
return (name1, name2) # O(1)
record.set_at(k, (name1, bday1)) # O(1)
return None # O(1)Outer loop runs \(n\) times; the inner loop runs \(k\) times on round \(k\):
\[O(n) + \sum_{k=0}^{n-1}\left(O(1) + k \cdot O(1)\right) = O(n^2)\]
Two moves, all semester:
The course builds your toolbox in that order: data structures and sorting, then graphs and shortest paths, then design paradigms, then the limits of computation. Keep a running inventory; by the final you will pattern-match problems to tools on sight.
HW0 is assigned today, due Monday, August 31 at 9:59 PM, via Canvas.
It is an individual prerequisite self-check, completed without collaboration or outside help. It is scored for feedback but does not count toward your final grade: it is a diagnostic that tells both of us where your background stands, and a chance to practice LaTeX before the graded work begins.
Written portions must be prepared in LaTeX (a template is provided in Canvas). This applies to all homework and exams in the course; the Getting Started with LaTeX guide on the course Resources page has you covered.
If HW0 feels rough, that is a signal to visit student hours or our TA’s help hours, this week, not week 6.
Six short problems, all prerequisite material:
count_long_subarrays(A) returns how many decreasing subarrays of A achieve the maximum length.
Example: for A = (6, 4, 2, 5, 9, 7, 3, 8, 1) the longest decreasing subarrays have length 3, and there are two of them: (6, 4, 2) and (9, 7, 3). So return 2.
Welcome to CS 351. It is going to be a great semester.
