Chapter 2 · Lectures

Algorithms, recursion, greedy, and DP

2.1 Introduction to Algorithms

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 Domain Problem Common algorithm


DNA analysis Longest shared subsequence Longest common substring E-commerce / search “Is this product in stock?” Binary search Navigation Fastest route A \to 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:

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

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

  3. Describe an approach in pseudocode or English. Steps, not syntax. If the approach doesn’t work on paper, C++ will not save it.

  4. Estimate the cost. Will this scale to the largest input the problem allows? If no, find a better approach before you write any code.

  5. Translate to C++. Now the language matters. Idiomatic vectors, bounds checking, .at() while debugging, algorithms from <algorithm> where they fit.

  6. 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 nn prime?

Watch the four habits in action on one problem.

Decomposition.

Big question (“is nn prime?”) into smaller yes/no questions: is it divisible by 2? by 3? by 4? … If any answer is yes, nn 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 nn (other than nn itself) can exceed n/2n/2, so we never need to test past there. In fact, no factor exceeds n\sqrt{n}, which is a much tighter bound.

Algorithm.

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 nn; 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+21 + 2, then +3+ 3, …

Pattern recognition.

Pair the numbers from the ends in: (1+100),(2+99),(3+98),(1 + 100), (2 + 99), (3 + 98), \ldots Each pair sums to 101, and there are 50 pairs. Total: 50101=505050 \cdot 101 = 5050.

Algorithm.

The closed-form n(n+1)2\dfrac{n(n+1)}{2} computes the sum in O(1)O(1) rather than O(n)O(n).

Abstraction.

The formula works for any positive nn, 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 step CT 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(n1)(n2)21n! = n \cdot (n-1) \cdot (n-2) \cdots 2 \cdot 1, with 1!=11! = 1 as the base.

Example 2: cumulative sum

Sum of 0+1+2++n0 + 1 + 2 + \cdots + n. Base case: sum to 00 is 00.

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:

  1. Base case. The base-case answer is correct by direct inspection.

  2. 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)O(\log n) comparisons — dramatically faster than the linear O(n)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(\phi^n) O(logn)O(\log n) Typical depth nn logn\log n Stack risk at n=106n = 10^6 overflow long before safe (depth 20\sim 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 Precaution Tradeoff


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 Guideline What 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×\times 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.

When to reach for a heuristic (and when not to)

::: center Reach for a heuristic when Use 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 Strategy How it decides Cost / guarantee


Greedy Best local choice, no reconsideration Fast; optimal only on certain problems Dynamic programming Remember optimal answers to subproblems, build up O(nstate size)O(n \cdot \text{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(nm)O(n \cdot 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]L[i][j] be the length of the longest common substring that ends at s[i] and t[j].

undefined
interactive mode active