Chapter 1 covered the mechanics of C++ — vectors, strings, loops. Chapter 2 starts asking a different question: given a problem, what sequence of steps actually solves it, and how do you compare one sequence against another? That is the subject of algorithms, and it is the real content of this course.
Problems vs. algorithms
Two words that get used interchangeably but mean different things.
Why this matters: picking the right algorithm is the job
Most interesting problems you will ever see have already been reduced to a small set of well-known subproblems, each with a well-studied algorithm. Half of being a good engineer is recognizing which known problem the thing in front of you actually is.
::: center
DomainProblemCommon algorithm
DNA analysis Longest shared subsequence Longest common substring
E-commerce / search “Is this product in stock?” Binary search
Navigation Fastest route A → B Dijkstra’s shortest path
Scheduling Which tasks can run first? Topological sort
Compilers / build What to rebuild after a change DFS on a dependency graph
Spell check / autocomplete Closest word to a typo Edit distance (DP)
:::
The CS 300 syllabus is essentially a tour of the right-hand column — sorting, searching, hashing, trees, heaps, graph traversal. Once you can name the tool, the implementation becomes mechanical.
What makes an algorithm “good”?
The dominant metric is runtime growth — how the number of steps changes as the input gets bigger. This is the subject of Chapter 2 proper.
NP-complete problems: when no fast algorithm is known
Some problems have resisted every attempt to find an efficient algorithm. These are the NP-complete problems.
2.2 Problem Solving
Knowing C++ is a prerequisite for writing code, but it is not what makes programs work. The hard part — the part people are actually paid for — is problem solving: figuring out a methodical sequence of steps that turns the input into the desired output. The programming language is just the notation you write that sequence down in, the way English is the notation a chef writes a recipe down in. Good recipes come from good cooking, not good English.
A non-programming example: matching socks
Three kinds of socks sit unsorted in a drawer. You want one matching pair.
A programming-flavored example: sorting name tags
1000 name tags, currently sorted by first name, need to be re-sorted by last name.
The 64-person greeting problem
An organizer wants every person at a 64-person meeting to greet every other person for 30 seconds. How long does this take?
A usable workflow
The approach this course will try to instill, roughly in order:
Read the problem until you can restate it in one sentence without looking at the prompt. If you cannot, you haven’t understood it yet; coding will not help.
Solve a tiny example by hand. Before writing code, walk through the problem with a three- or four-element input. This usually exposes missing details and suggests an algorithm.
Describe an approach in pseudocode or English. Steps, not syntax. If the approach doesn’t work on paper, C++ will not save it.
Estimate the cost. Will this scale to the largest input the problem allows? If no, find a better approach before you write any code.
Translate to C++. Now the language matters. Idiomatic vectors, bounds checking, .at() while debugging, algorithms from <algorithm> where they fit.
Test with the tiny example from step 2. If the output does not match, trace the loop ( methodology).
2.3 Computational Problem Solving
framed problem solving in general terms. This section narrows in on the specific style of thinking that computing demands, which cognitive-science and CS-education literature has settled on calling computational thinking. It is four habits — decomposition, pattern recognition, algorithms, and abstraction — applied together. The names are generic; the habits are not.
The input > process > output model
Every program, no matter how large, fits the same shape at the top level: take input, compute, produce output (and/or persistent side effects). Keeping this shape in mind while designing prevents the common trap of conflating “how data gets in” with “what we do with it.”
The four habits of computational thinking
Worked example: is n prime?
Watch the four habits in action on one problem.
Decomposition.
Big question (“is n prime?”) into smaller yes/no questions: is it divisible by 2? by 3? by 4? … If any answer is yes, n is not prime; if all are no, it is prime.
Pattern recognition.
Two useful observations:
We can stop the first time we see a divisor — we don’t need to check the rest.
No factor of n (other than n itself) can exceed n/2, so we never need to test past there. In fact, no factor exceeds n, which is a much tighter bound.
Algorithm.
C++
IsPrime(n): if n < 2: return false for d = 2 to sqrt(n): if n mod d == 0: return false return true
Abstraction.
The algorithm doesn’t depend on any specific value of n; it works for every positive integer. Expressed as a function it becomes a reusable tool.
Second example: sum of 1 to 100
Decomposition.
Add numbers one at a time: 1+2, then +3, …
Pattern recognition.
Pair the numbers from the ends in: (1+100),(2+99),(3+98),… Each pair sums to 101, and there are 50 pairs. Total: 50⋅101=5050.
Algorithm.
The closed-form 2n(n+1) computes the sum in O(1) rather than O(n).
Abstraction.
The formula works for any positive n, not just 100. It generalizes further to arithmetic sequences.
The loop from revisited
The workflow from the previous section is computational thinking in practice:
::: center
Workflow stepCT concept
Restate the problem (prerequisite)
Solve a tiny example by hand decomposition
Spot the structure pattern recognition
Write pseudocode algorithm
Generalize to a function abstraction
Estimate the cost pattern recognition (again)
Translate to C++ implementation
Test on the tiny example verification
:::
2.4 Recursive Definitions
A recursive algorithm solves a problem by reducing it to a smaller instance of the same problem and combining the answer. It is the same mental move as mathematical induction, and it unlocks a surprising number of problems that are awkward to express with loops alone — tree traversals, divide-and-conquer sorts, graph searches, backtracking puzzles. The entire back half of this course leans on it.
The anatomy of every recursive function
Example 1: factorial
n!=n⋅(n−1)⋅(n−2)⋯2⋅1, with 1!=1 as the base.
Example 2: cumulative sum
Sum of 0+1+2+⋯+n. Base case: sum to 0 is 0.
Example 3: reverse a vector by swapping from both ends
A recursive rewrite of the reversal loop from .
What can go wrong
When to reach for recursion
Recursion and induction — the same idea
A correctness argument for recursion mirrors a proof by induction:
Base case. The base-case answer is correct by direct inspection.
Inductive step. Assume the recursive call returns the correct answer for the smaller input. Show that the combine step produces the correct answer for the current input.
2.5 Recursive Algorithms
introduced the mechanics of recursion on small arithmetic examples. This section applies the same three-part template (base, reduce, combine) to two algorithms you will actually use: Fibonacci (a warning sign about naive recursion) and binary search (a textbook win for recursion). Read them side by side — same shape, wildly different efficiency.
Fibonacci: the cautionary tale
The definition is a recurrence, so the recursive implementation writes itself:
Binary search: the textbook win
Here recursion earns its keep. Binary search on a sorted array finds a key in O(logn) comparisons — dramatically faster than the linear O(n) scan from .
Tracing binary search on a concrete input
Preconditions and edge cases
Side-by-side: when recursion helps and when it hurts
::: center
Fibonacci (naive)Binary search
Recursive calls per step 2 1
Subproblem overlap massive none
Time complexity O(ϕn)O(logn)
Typical depth nlogn
Stack risk at n=106 overflow long before safe (depth ∼20)
:::
2.6 Data Privacy
This section interrupts the algorithms thread to cover professional ethics — specifically, how a working developer should think about confidential data. The material is short on C++ but the habits it teaches show up in every real job: which data you collect, where you store it, who can see it, and how to reason about the tradeoffs when those choices conflict.
”Alertness” as a professional habit
The textbook framing borrows a line from safety engineering: the worst failures are the ones a little alertness would have prevented. For a developer, “alertness” translates to a handful of reflexes:
Before writing code that collects a piece of data, ask whether it’s actually needed.
Before sending a file, email, or dataset, ask who else sees it along the way.
Before stepping away from a laptop with sensitive data open, lock the screen.
Before committing, check for credentials, keys, or customer data in the diff.
None of these are technical. All of them are prevented by a quick pause, and none of them take a second when they become habit.
The Company XYZ scenario (and how it should have gone)
The textbook scenario: a data analyst is hired to do a confidential job-satisfaction survey. Data collected: name, home address, supervisor, free-form criticism. Data stored: an unencrypted Excel spreadsheet on a laptop left unlocked in a break room. Data leaked: the spreadsheet, forwarded by an admin who saw it open. Fallout: harassment, lawsuit, resignations.
The two levers: collection and release
The tradeoffs are real
There is no zero-risk data practice. Every privacy protection costs something — inconvenience, reduced analytic power, slower development velocity — and the job is to consciously balance cost against risk.
::: center
PrecautionTradeoff
Drop identifying fields Lose some analyses (commute distance, per-supervisor complaint patterns).
Encrypt the file Extra step every time you open it; password must be remembered and rotated.
Collect fewer responses Smaller sample, less statistical power.
Refuse to share a dataset Slows collaboration; may block a legitimate use.
Retain data longer More value for longitudinal analysis, more exposure if breached.
:::
A short history of things that went wrong
The scale of real breaches is hard to picture:
::: center
2013, Target 70M+ customer records including credit-card data; stemmed from a compromised HVAC vendor login.
2013, Snowden / NSA Thousands of classified documents leaked by an insider with legitimate access.
2014, eBay 148M accounts; passwords encrypted but email and address in the clear.
2014, Sony Pictures Terabytes including emails, salaries, and HR data; a senior executive lost their job over quoted emails.
2015, Anthem 80M records including SSNs and health info.
2015, Ashley Madison 30M user identities of a service whose entire value was discretion; several suicides attributed in part to the exposure.
2017, Equifax 147M Americans’ SSNs and credit data; root cause a known Apache Struts vulnerability left unpatched.
2021, Facebook 533M user records including phone numbers scraped via a contact-import misuse.
:::
What this means for you on CS 300 and beyond
Course projects rarely handle real PII, but the habits are worth forming now:
Don’t commit credentials, API keys, or .env files to git. Add them to .gitignore before you run your first git add.
Use environment variables or a secrets manager for API tokens — never string literals in source.
When a program reads user data, think about where it lives after the program exits: in memory? in a temp file? in a log?
Treat student test data with the same care you would real data, so the habits hold when the data does matter.
2.7 Ethical Guidelines
covered the narrow question of keeping confidential data confidential. This section widens the frame: once you have data and are doing something with it, what obligations come with the work? The textbook reaches for the American Statistical Association’s Ethical Guidelines for Statistical Practice, which is a good reference point even though CS 300 is not a statistics course — every engineer who ships a system that affects people is doing applied statistics whether they call it that or not.
The one-line summary
The ASA opens its guidelines with a sentence worth memorizing:
Good statistical practice is fundamentally based on transparent assumptions, reproducible results, and valid interpretations.
Those three phrases are the compressed form of everything that follows. When a result looks impressive, ask: were the assumptions stated? can someone else reproduce it? does the interpretation actually follow from the numbers?
The five categories
The ASA guidelines organize obligations into five buckets. Roughly: what you owe your work, your data, your audience, your subjects, and your field.
1. Professional integrity and accountability.
Pick the sampling and analytic approach that actually answers the question, not the one that gives the answer you wanted.
Identify and reduce sources of bias. Disclose what you can’t eliminate.
Disclose conflicts of interest — financial, political, or otherwise. Readers get to discount your work accordingly.
2. Integrity of data and methods.
Document what you did. Not “document in principle” — the preprocessing, the filters, the parameters, the cutoffs.
Report results truthfully, including the uncertainty. “Our model is 94% accurate” is meaningless without a confidence interval, a baseline, and the data it was measured on.
Share data and code when you can, redacting or aggregating to preserve privacy when you can’t.
3. Responsibilities to the public and/or client.
Explain the tradeoffs of the approach you chose, including what it cannot tell them.
Make useful knowledge broadly available when it doesn’t compromise privacy or legitimate confidentiality.
4. Responsibilities to research subjects.
Stay current on best practices for protecting the people whose data you hold.
Protect privacy and confidentiality of subjects even when doing so costs you analytic power.
5. Allegations of misconduct.
Don’t condone — or cover for — bad practice by colleagues.
Distinguish honest error and genuine disagreement from misconduct. They look similar and are judged very differently.
Don’t retaliate against whistleblowers.
Connecting the categories to CS 300 work
“Statistical practice” sounds distant from a sophomore algorithms class. It shouldn’t. A few concrete mappings:
::: center
GuidelineWhat it looks like in CS 300 / future work
Transparent assumptions Document your time complexity, your input size assumptions, and what “works” means (average? worst case? specific distribution?).
Reproducible results Fixed random seeds in tests, deterministic hash-table iteration, version-pinned dependencies.
Valid interpretation “Sort X finished 2× faster” is a claim about the specific input, machine, and compiler flags — not a universal truth.
Disclose conflicts If you generated your test data with the same code you’re benchmarking, say so.
Protect subjects Don’t commit student records, user IDs, or real customer data to your course repo.
Don’t retaliate, don’t condone If a teammate ships a function that rounds metric values “to look better,” call it out in review.
:::
The classic ways analyses go bad
Unethical analysis often doesn’t look like fraud — it looks like a slightly wrong choice at each of five steps that compound into a misleading conclusion.
A short CS 300 reproducibility checklist
Even in course projects, form the habits now:
Seed any randomness you use in benchmarks or tests.
Record compiler, flags, CPU, and input size alongside every performance number.
Version the dataset. “The bids.csv we were given” is not enough — keep a hash or a dated copy.
Rerun the whole pipeline from scratch occasionally to make sure it still produces the reported results.
Commit the analysis code next to the numbers it produced, not separately.
2.8 Heuristics
ended on a note: some problems (the NP-complete ones) have no known efficient exact algorithm. What do you do when you still have to ship? You use a heuristic — an algorithm that deliberately trades optimality for speed or simplicity. Heuristics are everywhere in production code: route planners, ranking systems, allocators, schedulers, compilers, game AI. When the exact answer is too expensive to compute, a provably-pretty-good answer in a reasonable amount of time is the whole game.
The knapsack problem: the canonical example
A greedy heuristic for knapsack
Self-adjusting heuristics
A different flavor of heuristic: instead of approximating an answer, the algorithm reorganizes its data structure over time based on how it’s being used, so common operations get faster.
Worked example: move-to-front on a linear search
When to reach for a heuristic (and when not to)
::: center
Reach for a heuristic whenUse the exact algorithm when
The problem is NP-hard and inputs are large. The problem has a polynomial-time exact algorithm (most of this course).
“Pretty good, fast” is more valuable than “optimal, slow.” Optimality is required by the spec (safety, financial, legal).
You can measure how close the heuristic gets to optimal and it’s good enough. You can’t measure closeness and a bad answer has real consequences.
The algorithm will run on streaming / online data where you can’t see the whole input. The input is fully available and small enough to process exactly.
:::
2.9 Greedy Algorithms
introduced heuristics — the idea of accepting non-optimal answers for speed. A greedy algorithm is the most common style of heuristic: at each step, commit to the choice that looks best right now, without looking ahead. Sometimes this lands you at the globally optimal answer (change-making, fractional-knapsack, activity selection, shortest paths). Sometimes it doesn’t (0-1 knapsack from ). Knowing which problems are in the first bucket and which aren’t is the real skill.
Example 1: making change (and why US coins are special)
Example 2: fractional knapsack — greedy is optimal
Example 3: activity selection — and why “earliest finish” wins
When greedy actually works
There is a pattern behind these examples. Greedy is provably optimal on problems with one of two underlying structures:
Matroid structure. Fancy name, simple idea: if the locally optimal choice is always part of some globally optimal solution, and “independent subsets” have an exchange property. Change-making with US coins, minimum-spanning-tree (Kruskal, Prim, later in course), and various scheduling problems have it.
Greedy-choice property + optimal substructure. Same two ingredients that justify dynamic programming, minus the need to enumerate every subproblem. Activity selection fits here.
Greedy vs. other strategies
::: center
StrategyHow it decidesCost / guarantee
Greedy Best local choice, no reconsideration Fast; optimal only on certain problems
Dynamic programming Remember optimal answers to subproblems, build up O(n⋅state size); optimal when subproblems overlap
Divide and conquer Split, recurse, combine Optimal; works when subproblems are independent
Backtracking Try choices, undo bad ones Exponential worst case but often prunable
Branch & bound Backtracking + bound pruning Exact; can be practical for small NP-hard inputs
:::
2.10 Dynamic Programming
Greedy () makes one choice at each step and never looks back. Pure recursion () recomputes the same subproblems over and over. Dynamic programming (DP) splits the difference: it solves every subproblem once, stores the answer, and assembles the final answer from those stored pieces.
When the problem has overlapping subproblems and optimal substructure, DP turns exponential-time recurrences into polynomial algorithms — usually a dramatic improvement.
Example 1: Fibonacci, fixed
showed naive recursive fib blowing up exponentially because fib(n-1) and fib(n-2) recompute overlapping work. DP fixes this two ways.
Top-down memoization.
Keep a cache, check it before recursing:
Bottom-up tabulation.
Build the answers iteratively from the base cases up:
Example 2: longest common substring (LCS)
A classic DP where the benefit is overwhelming. Naive recursion is exponential; DP is O(n⋅m) in the two string lengths. This algorithm underlies real-world tools: DNA sequence alignment, git’s rename detection, plagiarism detection.
The recurrence.
Let L[i][j] be the length of the longest common substring that ends ats[i] and t[j].