← Writing & notes

FROM THE NOTEBOOK

Algorithms - MIT 6.006

MIT 6.006 Introduction to Algorithms, Spring 2020 - YouTube

This course provides a rigorous and comprehensive introduction to the design and analysis of fundamental algorithms and data structures. The curriculum is structured to build a strong foundation in computational thinking, moving from basic tools for storing and retrieving data to advanced paradigms for solving complex problems.
The course begins by establishing a formal framework for evaluating algorithms, centered on the core principles of correctness and efficiency. Students learn the language of asymptotic analysis (Big O, Omega, and Theta notation) and operate within the Word RAM model of computation to analyze performance in a machine-independent manner.
The first major unit focuses on the implementation of two primary interfaces: sequences (for maintaining extrinsic, user-defined order) and sets (for intrinsic, key-based queries). A series of data structures are explored, each presenting a fundamental trade-off. Simple arrays offer constant-time random access but suffer from linear-time updates, while linked lists provide constant-time updates at their ends but have linear-time access. This trade-off is elegantly resolved by dynamic arrays and the concept of amortized analysis. For sets, the course contrasts the O(n) search of unsorted arrays with the O(log n) efficiency of binary search on sorted arrays, thereby motivating the need for efficient sorting algorithms. Classic O(n^2) sorts like Selection and Insertion Sort are contrasted with the optimal O(n log n) performance of Merge Sort.
To break the Ω(n log n) barrier for comparison-based operations, the course introduces hashing, which provides constant expected time for dictionary operations (find, insert, delete) by mapping a large universe of keys to a small table, with chaining to resolve collisions and universal hashing to guarantee performance through randomization. For integer-specific data, the course explores linear-time sorting algorithms like Counting Sort and Radix Sort, which operate outside the comparison model. This unit culminates in the study of balanced binary search trees (AVL trees), a powerful, versatile data structure that achieves O(log n) worst-case time for both dynamic updates and ordered queries, effectively providing the "best of all worlds." The technique of subtree augmentation is introduced to further enhance the capabilities of these trees, enabling efficient solutions to complex problems like index-based queries in dynamic sequences.
The second unit transitions to graph algorithms, modeling problems where relationships between data points are explicit. Students learn the standard representations (adjacency lists) and master the two fundamental traversal methods: Breadth-First Search (BFS) and Depth-First Search (DFS). BFS is applied to solve the single-source shortest path (SSSP) problem on unweighted graphs, while DFS is used for applications like finding connected components, performing topological sorts on Directed Acyclic Graphs (DAGs), and detecting cycles. The focus then shifts to weighted shortest paths, presenting a suite of algorithms tailored to different graph properties: DAG Relaxation for acyclic graphs, Dijkstra's algorithm for graphs with non-negative weights, and the more general Bellman-Ford algorithm for graphs with arbitrary weights, which can also detect negative-weight cycles. The unit concludes with Johnson's algorithm for the All-Pairs Shortest Paths (APSP) problem, which uses a clever reweighting technique to enable the efficient application of Dijkstra's algorithm.
The final unit focuses on a powerful paradigm for designing new algorithms: dynamic programming. Presented through the systematic SRTBOT framework, DP is framed as solving problems recursively by breaking them into a polynomial number of overlapping subproblems and using memoization to avoid redundant computation. Students learn to identify common subproblem patterns (prefixes, suffixes, substrings, integer parameters) and to expand the subproblem state space to "remember" necessary information, tackling a wide range of optimization and combinatorial problems such as Knapsack, Longest Common Subsequence, and game theory scenarios.
The course concludes with a look at computational complexity, introducing the classes P, NP, and the concept of NP-completeness. This provides context for the limits of efficient computation, formalizing the notion of "hard" problems for which no polynomial-time solutions are known. Finally, a capstone lecture connects the course's foundational principles to cutting-edge research in fields like computational geometry, computer graphics, and robotics, illustrating that the material covered is not merely academic but the essential toolkit for innovation across the landscape of computer science.

L1: Introduction

The lecture begins with an introduction from one of the instructors, Jason Ku. He mentions that he will be teaching the course alongside two other faculty members, Eric Demaine and Justin Solomon. He will be leading the first lecture, with the other instructors taking the subsequent two.
Core Concepts of the Course
The primary goal of the course is to equip students with the ability to solve computational problems. However, it goes beyond just finding solutions. Key aspects of the course include:

Defining Problems and Algorithms
A computational problem is formally defined as a binary relation between a set of inputs and a set of outputs. This means for each input, there can be one or more correct outputs. Problems are typically defined by a predicate that can verify if a given output is correct for a given input. The course focuses on general problems with arbitrarily large inputs.
An algorithm is described as a function or a procedure that takes an input and produces a single, correct output that satisfies the problem's definition. It is a fixed-size procedure that can handle inputs of any size.
Example: The Birthday Problem
To illustrate these concepts, the "birthday problem" is introduced: determining if any two students in a classroom share the same birth time. An algorithm is proposed:

  1. Maintain a record of birthdays encountered so far.
  2. Interview students one by one.
  3. For each student, check if their birthday is already in the record.
  4. If a match is found, return the pair and stop.
  5. If no match is found after checking the entire record, add the student's birthday to the record and move to the next student.
  6. If all students are interviewed without finding a match, return that there is no match.

Proving Correctness with Induction
To prove that this algorithm is correct for any number of students, the use of mathematical induction is necessary. The process involves:

Analyzing Algorithm Efficiency
The efficiency of an algorithm is not measured by the time it takes a specific computer to run it, as this depends on the hardware. Instead, efficiency is measured by counting the number of fundamental operations the algorithm performs. This is where asymptotic analysis comes into play, which evaluates the performance of an algorithm as the input size grows.
Key notations in asymptotic analysis are:

Common performance categories for algorithms include:

Model of Computation: Word RAM
To have a standardized way of counting operations, a model of computation called the word RAM is used. This model assumes a computer with:

Course Overview
The lecture concludes with a brief overview of the course structure. Students will learn two main strategies for solving algorithmic problems:

  1. Reducing the problem to a known problem that can be solved with existing algorithms (e.g., using data structures, sorting, or graph searches).
  2. Designing a new, recursive algorithm.

The course is divided into three main sections, each corresponding to a quiz:

L2: Data Structures and Dynamic Arrays

The lecture, led by instructor Erik Demaine, shifts focus from general algorithmic concepts to the specifics of data structures.
1. Interface vs. Data Structure
The lecture begins by drawing a crucial distinction between an interface and a data structure:

The course will focus on two primary interfaces, sets and sequences, and two main data structure tools to implement them: arrays and pointers. This lecture focuses on the sequence interface.
2. The Static Sequence Interface and Static Arrays
A static sequence is an ordered collection of a fixed number of items, n. The interface for a static sequence supports the following operations:

The most natural data structure to implement this interface is a static array. In the Word RAM model of computation, memory is treated as a large array of fixed-size words (e.g., 64-bit words). A static array is implemented as a consecutive block of memory. This contiguous storage allows for highly efficient access:

A key assumption of the Word RAM model is that the machine's word size (w) must grow with the problem size (n), specifically w must be at least log n. This ensures that a single word is large enough to hold a memory address that can point to any of the n items.
3. The Dynamic Sequence Interface
The lecture then introduces a more flexible interface, the dynamic sequence, where the number of items can change. This interface supports all the static operations plus two new ones:

Special cases of these operations, such as inserting or deleting at the beginning (insert_first, delete_first) or end (insert_last, delete_last), are particularly important because they can sometimes be implemented more efficiently than the general case.
4. Solution 1: Linked Lists
The first data structure considered for implementing a dynamic sequence is the linked list.

Linked lists and static arrays have complementary strengths: linked lists are good for dynamic updates at the ends, while static arrays are good for random access (get_at/set_at).
5. Solution 2: Dynamic Arrays
The final goal is to create a data structure that combines the strengths of both: the dynamic array. This is the data structure that underlies Python's list type.

6. Amortized Analysis
While doubling the array size is a linear-time operation, it happens infrequently. The cost of these expensive operations can be "spread out" over the many cheap, constant-time insertions that preceded them. This leads to the concept of amortized analysis.

7. Performance Summary
The lecture concludes with a comparison of the three data structures discussed for the sequence interface:

Operation Static Array Linked List Dynamic Array
get_at / set_at Θ(1) Θ(n) Θ(1)
insert_first / delete_first Θ(n) Θ(1) Θ(n)
insert_last / delete_last Θ(n) Θ(1)* Θ(1) amortized
insert_at / delete_at Θ(n) Θ(n) Θ(n)

*With tail pointer augmentation.
Dynamic arrays successfully provide the best of both worlds for random access and for adding/removing elements at the end, making them a highly effective and widely used data structure.

L3: PS 1: Asymptotic Behaviour of Functions

Instructor Jason Ku begins by explaining the purpose of these new Friday problem sessions. They are designed to bridge the gap between the foundational concepts presented in lectures and the practical application of those concepts required in the problem sets. The goal is to demonstrate how instructors approach and solve problems, providing students with a model for their own work. These recorded sessions replace previously recorded recitations, allowing recitations to be a more interactive and unrecorded "safe space" for students.
Problem 1: Asymptotic Order of Growth
This problem requires ordering sets of functions based on their asymptotic growth rate. Functions that are asymptotically equivalent (Θ) are grouped together in a set.
Part A
The first set of functions is ordered using the general principle that logarithmic factors grow slower than polynomial factors, which in turn grow slower than exponential factors. The final ordering presented is:

  1. f1 = n^(log n)
  2. f5 = n * log(n)
  3. f2 = n * log^2(n)
  4. f3 = n^2
  5. f4 = 2^(2n)

Part B
This part involves functions with factorials and binomial coefficients ("n choose k"). The key strategy is to convert each function into a more familiar form to allow for comparison.

Problem 2: Black Box Data Structure Operations
This problem provides a "black box" data structure that supports a sequence interface with insert_first, insert_last, delete_first, and delete_last all in Θ(1) time. The task is to implement new, higher-level operations using only these primitive functions.

Problem 3: A Data Structure with the "Best of Both Worlds"
The challenge is to design a data structure that combines the advantages of dynamic arrays (Θ(1) random access) and linked lists (Θ(1) front/back modifications). The goal is a structure with Θ(1) worst-case lookup and Θ(1) amortized time for insertions and deletions at both ends.

Problem 4: Reordering a Singly Linked List
This is a coding problem where a singly linked list containing 2n nodes must be modified. The task is to reverse the order of the last n nodes in Θ(n) time and with Θ(1) extra space (meaning no new nodes or large data structures can be created).

Jason Ku then performs a live coding demonstration of this solution in Python, successfully implementing the logic and passing all test cases.

L4: Sets and Sorting

The lecture is delivered by the third instructor, Justin. He begins by revisiting a core theme from the first lectures: the distinction between an interface and a data structure.

This distinction is crucial because a single interface (like a set) can be implemented by many different data structures, each with its own trade-offs in efficiency and memory usage.
2. The Set Interface
The main focus of the lecture is the Set Interface, which is conceptually a container for a collection of items.

3. Data Structures for Implementing a Set
Two initial data structures are proposed to implement the set interface, highlighting a key performance trade-off.
Data Structure 1: Unsorted Array

Data Structure 2: Sorted Array

This realization shifts the focus of the lecture to the sorting problem: how can we efficiently take an unsorted array and sort it?
4. Sorting Algorithms
The lecture explores three sorting algorithms to understand their efficiency.
Algorithm 1: Permutation Sort (A "Bad" Example)

Algorithm 2: Selection Sort

Algorithm 3: Merge Sort (The "Good" Example)

This n log n runtime is a major benchmark for comparison-based sorting algorithms and makes the Sorted Array a highly efficient and practical data structure for implementing the Set Interface.

L5: Hashing

The lecture begins by revisiting the Set Interface, which stores items and allows them to be queried by their intrinsic key. Previous data structures for this interface were:

The central question for the lecture is: Can we make
2. A Lower Bound for Comparison-Based Search
First, a theoretical limit is established. Within the Comparison Model of computation, the answer is no.

3. Breaking the Barrier: The Direct Access Array
To achieve a search time faster than log n, we must use an operation that is outside the comparison model. The Word RAM model provides such an operation: constant-time random memory access.
This leads to the idea of a Direct Access Array:

4. The Core Idea of Hashing
Hashing is the solution to the space problem of direct access arrays.

5. Collision Resolution by Chaining
The lecture focuses on one strategy for handling collisions: Chaining.

6. Universal Hashing: The Power of Randomness
The key to keeping chains short is to use a good hash function.

This leads to the concept of Universal Hashing.

7. Analysis and Dynamic Hashing
Using a universal hash family gives a powerful probabilistic guarantee.

L6: PS 2

The session, led by instructor Justin, aims to provide practical examples of how to approach and solve problems similar to those on homework assignments and exams. The focus is on translating problem descriptions into algorithmic tasks and applying the theoretical concepts learned in lectures.
Problem 1: Solving Recurrences with the Master Theorem
This problem focuses on solving recurrence relations, which are fundamental to analyzing the runtime of recursive algorithms. The primary tool used is the Master Theorem, a "sledgehammer" formula for solving recurrences of the form T(n) = aT(n/b) + f(n). Two methods are demonstrated for each part: direct application of the Master Theorem and analysis using a recursion tree.
Review of the Master Theorem
The theorem compares the function f(n) (the work done at each step) to the critical function n^(log_b a).

  1. Case 1: If f(n) is polynomially smaller than n^(log_b a), then T(n) = Θ(n^(log_b a)).
  2. Case 2: If f(n) is asymptotically equal to n^(log_b a), then T(n) = Θ(n^(log_b a) * log n).
  3. Case 3: If f(n) is polynomially larger than n^(log_b a), then T(n) = Θ(f(n)).

Part A: T(n) = 2T(n/2) + O(sqrt(n))

Part B: T(n) = 8T(n/4) + O(n * sqrt(n))

Problem 2: The Infinity Stones Search
This problem requires finding an unknown planet with index k in an infinite, ordered set of planets (1, 2, 3, ...). The only allowed operation is to query an oracle on any planet p to learn if k is greater or smaller than p. The goal is an O(log k) runtime.

The total time is O(log k) + O(log k) = O(log k), which meets the requirement.
Problem 3: Fadobe Image Editor
This problem involves designing a data structure to manage layers in an image editor. It must support several operations with specific runtime constraints, most notably a move_below(x, y) operation in O(log n) time.

Problem 4: The Brick-Blowing Wolf
This problem, wrapped in a story, asks for an efficient algorithm to calculate "damage." The core task is: for each element H[i] in an array, count how many elements H[j] (where j > i) are smaller than H[i].

L7: Linear Sorting

The lecture begins with a review of the previous session on hashing and its place among implementations of the Set Interface.

2. A Lower Bound for Comparison-Based Sorting
The lecture then shifts focus from searching to sorting and asks if the Θ(n log n) runtime of Merge Sort can be improved upon.

3. Sorting in Linear Time: Direct Access Array Sort
To sort faster than n log n, one must step outside the Comparison Model and use integer properties and random access.

4. Radix Sort: Handling Larger Keys
The lecture then explores how to sort integers from a larger key space, for example, U ≤ n².

5. Counting Sort: A Linear-Time Stable Sort
To implement Radix Sort, a stable, linear-time sorting subroutine is needed. Counting Sort fits this requirement perfectly.

6. Radix Sort: The Final Algorithm
Radix Sort combines the tuple sort idea with Counting Sort as its subroutine.

Conclusion: Radix Sort provides a way to sort integers in linear time, breaking the n log n barrier by avoiding comparisons and leveraging the integer nature of the keys, but only when the keys are not exponentially larger than the number of items.

L8: PS 3

The session, led by instructor Erik Demaine, focuses on solving more creative and conceptual problems related to hashing and sorting, skipping a more mechanical introductory problem to focus on algorithmic thinking. The general approach for each problem is outlined as:

  1. Translate the word problem into a formal algorithmic goal.
  2. Brainstorm ideas and strategies.
  3. Verify the details and analyze the final solution.

Problem 3.2: Hash Sequence
Goal: Implement a sequence data structure using a set data structure (specifically, a hash table) as a black box. The implementation must meet specific runtime requirements for various sequence operations.

Problem 3.3: Critter Sort (A Pokémon Parody)
Goal: For n "critters," determine the fastest correct sorting algorithm for four different types of keys. This problem tests the ability to choose between comparison-based sorts (like Merge Sort) and integer-based sorts (like Radix Sort).

Problem 3.4: Cube Summing
Goal: Given a set S of n non-negative integers and a target h, find a pair of numbers in S that sum to a value related to h.

Problem 3.5: Po-k-er Hands
Goal: Analyze a card-dealing process where a deck is cut randomly, k cards are dealt, and then sorted to form a "hand."

L9: Binary Trees 1

The lecture begins by reviewing the data structures covered so far and their limitations, setting the stage for a more powerful solution: binary trees.

2. What is a Binary Tree?
A rooted binary tree is defined as a collection of nodes connected by pointers.

The primary advantage of a tree structure over a list is its potential for a low height (h). While a list-like tree has h = O(n), a balanced tree can achieve h = O(log n), enabling fast traversal from the root to any node.
3. The In-Order Traversal: An Implicit Sequence
The crucial concept that allows a tree to represent a linear order is the in-order traversal.

4. Basic Tree Operations (Runtime: O(h))
The lecture introduces several fundamental operations for navigating and manipulating the tree, all of which run in time proportional to the height of the tree, O(h).

5. Connecting Trees to the Set and Sequence Interfaces
The final part of the lecture connects these tree operations back to the high-level interfaces.

Today's Conclusion: All fundamental set and sequence operations can be implemented to run in O(h) time. The next lecture will focus on techniques to guarantee that the tree's height , thus achieving the final goal of logarithmic time performance.

L10: Binary Trees 2

The lecture begins with a recap of the previous session on binary trees.

2. Binary Trees for Sets and Sequences
The lecture clarifies how binary trees are used to implement the two main interfaces.

3. Subtree Augmentation: The Key to Sequences
To make sequence operations efficient, a powerful technique called subtree augmentation is introduced.

With the size property augmented on every node, the get_at(i) operation can be implemented efficiently. By comparing the target index i to the size of the left subtree, we can decide whether to go left, right, or stop (if the root is the target) in O(1) time at each step, leading to an overall O(h) runtime.
4. The Problem: Unbalanced Trees
The major unresolved issue is that all operations run in O(h) time. While a "perfect" binary tree has h = O(log n), a degenerate, list-like tree can have h = O(n). The goal is to keep the tree balanced to guarantee that h is always O(log n).
5. Rotations: The Rebalancing Tool
A new tree manipulation tool is introduced: rotations.

6. AVL Trees: Height-Balanced Trees
AVL trees are a specific type of self-balancing binary search tree that uses rotations to maintain a strict balance property.

7. Maintaining the Height-Balance Property
The final challenge is to show how to maintain the height-balance property after an insertion or deletion.

After performing the appropriate rotation(s) at the lowest unbalanced node, the height of that subtree is restored to what it was before the modification. This remarkable property means that no further fixes are needed higher up the tree. The entire rebalancing process is completed by walking up the ancestor path once, taking O(h) time.
Conclusion
By augmenting nodes with their height and using rotations to fix imbalances, AVL trees guarantee that h remains O(log n). Since all the underlying sequence and set operations were shown to run in O(h) time, this means that AVL trees can perform all of these operations in worst-case time, achieving the "best of all worlds" goal.

L11: PS 4

Instructor Jason Ku begins by contextualizing the material. With the introduction of binary trees, the core set of data structures for implementing the Set and Sequence interfaces is now complete. He presents a performance comparison of these data structures (arrays, linked lists, dynamic arrays, hash tables, and balanced binary trees) by running a test script that measures the actual time taken for various operations. The results visually confirm the theoretical runtimes discussed in class:

2. Problem 4-1: Sequence AVL Tree Deletion
This is a mechanical problem designed to test understanding of AVL tree operations on a sequence tree.

3. A Brief Note on Binary Heaps
A preview of binary heaps is given, as they will be relevant to an upcoming problem set. A heap is presented as a "black box" that implements a priority queue interface.

4. Problem 4-2: Fick Nury's Revengers
This problem involves finding the log n superheroes with the most "extreme" opinions from a list of n opinions (positive or negative numbers).

5. Problem 4-3: SCLR Auction
This problem requires designing a data structure to manage bids in an auction. It must support placing/updating bids and, crucially, retrieving the total revenue from the top k highest bids in O(1) time. Updates must be O(log n).

6. Problem 4-4: Receiver Roster
This problem involves managing a roster of football players, each of whom has played in a set of games and scored points. The goal is to find the player with the k-th highest performance (average points per game).

This complex, multi-layered solution demonstrates how different data structures and augmentation techniques can be combined to solve sophisticated query problems.

L12: Binary Heaps

The lecture begins by introducing the priority queue, an interface that is a subset of the more general Set Interface.

2. Existing Data Structures for Priority Queues
Several data structures already covered in the course could implement a priority queue, each with different performance trade-offs.

Data Structure build insert delete_max
Unsorted Array O(n) O(1) amortized O(n)
Sorted Array O(n log n) O(n) O(1) amortized
Set AVL Tree O(n log n) O(log n) O(log n)

The goal is to find a data structure that combines the fast insertion of an unsorted array with the fast deletion of a sorted array, achieving O(log n) for both. While a Set AVL tree already does this, the lecture aims to find a simpler and more space-efficient solution.
3. The Motivation: In-Place Sorting
The primary motivation for developing a new data structure is to create a new, efficient sorting algorithm.

4. Binary Heaps: The Data Structure
The new data structure is the binary heap. It uniquely combines the properties of an array and a tree.

5. Heap Operations (Runtime: O(log n))
With the heap structure defined, the core priority queue operations are implemented.

6. Heapsort and Linear-Time Build

  1. Start with the array A as a complete binary tree that does not yet satisfy the max-heap property.
  2. Iterate through the nodes from the last non-leaf node up to the root.
  3. For each node, call max_heapify_down to fix the heap property for the subtree rooted at that node.
  4. A careful analysis shows that the total work done is a sum over the heights of the nodes, which surprisingly evaluates to O(n), not O(n log n). This is because most nodes are near the bottom of the tree (leaves) and require little to no work to heapify.

L13: Breadth-First Search

The lecture marks the beginning of the second part of the course, focusing on graph theory.

2. Graph Terminology and Properties
Several key terms and properties are defined to build a formal vocabulary for discussing graphs.

3. Representing Graphs on a Computer
Different data structures can be used to store a graph, each with its own performance characteristics.

4. The Shortest Path Problem
The lecture introduces the canonical problem of finding paths in a graph, specifically the shortest path.

The lecture focuses on solving the SSSP problem, as its solution can be used to solve the other two.
5. The Shortest Path Tree and Level Sets

6. Breadth-First Search (BFS)
Breadth-First Search (BFS) is the algorithm presented for solving the SSSP problem on an unweighted graph (where all edge lengths are assumed to be 1).

L14: Quiz 1

Instructor Jason Ku begins the session by framing it as a review for the upcoming quiz. He reiterates the core goals of the course:

  1. Solve Computational Problems: Develop algorithms to address specific challenges.
  2. Prove Correctness: Argue that the proposed solution is correct.
  3. Analyze Efficiency: Demonstrate that the solution is efficient.
  4. Communicate Ideas: Clearly explain the algorithm, its correctness, and its efficiency.

He categorizes the types of problems seen in the course into three levels:

2. General Quiz-Taking Strategies
Ku offers several pieces of advice for tackling the quiz:

3. Review of Core Material: The "Standard Library"
The session includes a comprehensive review of the "standard" algorithms and data structures that students are expected to know and be able to use as black boxes. These are summarized in tables.

4. Problem 1: Restaurant Wait List
Goal: Design a data structure to manage a restaurant wait list with the following operations, all in O(1) time (worst-case, expected, or amortized).

5. Problem 2: Rainy Research
Goal: Design a data structure to record rainfall measurements (rainfall r, latitude l, time t) and support two operations:

L15: Depth-First Search

The lecture begins with a recap of fundamental graph concepts and the BFS algorithm from the previous session.

2. Depth-First Search (DFS) for Reachability
The lecture introduces a new graph traversal algorithm, Depth-First Search (DFS), and motivates it with the reachability problem: which vertices are reachable from a given source s?

3. Full DFS and Connected Components
The basic DFS algorithm only finds vertices reachable from a single source. To analyze an entire graph, especially a disconnected one, a "full" DFS is needed.

4. Topological Sort for Directed Acyclic Graphs (DAGs)
The lecture shifts to a key application of DFS on a specific type of directed graph: the Directed Acyclic Graph (DAG). A DAG is a directed graph with no cycles.

5. Cycle Detection
The final application of DFS is cycle detection in a directed graph. The algorithm for topological sort provides a direct way to detect cycles.

L16: Weighted Shortest Paths

The lecture begins with a review of the graph algorithms covered so far, all of which operate on unweighted graphs where path length is simply the number of edges.

2. Introduction to Weighted Shortest Paths
The core of the lecture is the generalization of the shortest path problem to weighted graphs.

3. The Problem of Negative Weight Cycles
A major complication arises with weighted graphs that is not present in unweighted graphs: negative weight cycles.

4. A Roadmap for Weighted SSSP Algorithms
The lecture provides a roadmap for the next few sessions, outlining different algorithms for different types of weighted graphs.

  1. Positive Integer Weights (BFS Reduction): If all edge weights are small, positive integers, we can transform the weighted graph into a larger unweighted graph (by replacing an edge of weight k with k-1 new vertices and k new edges of weight 1) and solve it with BFS in linear time.
  2. Directed Acyclic Graphs (DAGs) (Today's Topic): For DAGs, SSSP can be solved in O(|V| + |E|) time, even with negative edge weights, because DAGs have no cycles by definition.
  3. General Graphs with Negative Weights (Bellman-Ford Algorithm): For general graphs that may contain negative weight cycles, this algorithm can solve SSSP or detect the presence of such cycles. It runs in O(|V| * |E|).
  4. General Graphs with Non-Negative Weights (Dijkstra's Algorithm): If all edge weights are guaranteed to be non-negative, this more efficient algorithm can solve SSSP in O(|E| log |V|) time.

5. The Concept of Relaxation
The core mechanic behind most weighted shortest path algorithms is relaxation.

6. SSSP on a DAG in Linear Time
The main algorithm presented is for solving the SSSP problem on a Directed Acyclic Graph (DAG).

The lecture concludes by noting that the parent pointers for the shortest path tree can be reconstructed from the final distance estimates in linear time, so future algorithms will focus only on computing the distances.

L17: PS 5

The session, led by instructor Justin Solomon, covers several graph theory problems related to Breadth-First Search (BFS) and Depth-First Search (DFS). The problems are presented as word problems, emphasizing the key skill of translating a narrative into a formal algorithmic task.
Problem 1: Radius of a Graph
Goal: This problem involves computing the "radius" of a connected, undirected graph G.

Problem 2: Internet Investigation
Goal: Given r routers, some of which are "entry points," and wires of varying lengths connecting them, find the sum of the latencies for all routers. The latency of a router is its shortest path distance to any entry point. The total length of all wires is at most 100r. The algorithm should run in O(r) time.

Problem 3: Potry Harter's Labyrinth
Goal: Given a labyrinth of n rooms connected by doors (some "enchanted," some not), find the minimum number of enchanted doors that must be opened to visit every room. Every room has at most 4 doors.

Problem 4: Purity Atlantic Itinerary
Goal: Given c cities and f flights, find the flight itinerary that visits a home city H and three other specific cities (A, B, C) with the minimum number of connections.

Problem 5: Pocket Cube
Goal: Analyze the "state graph" of a 2x2x2 Rubik's cube (a pocket cube).

L18: Bellman-Ford

The lecture begins with a recap of weighted shortest paths and a roadmap for the upcoming algorithms.

2. Simple Shortest Paths
A key preliminary concept is established: if a shortest path has a finite weight, there must exist a simple shortest path (one that does not repeat vertices).

3. k-Edge Distances and Witnesses
This observation motivates a new definition: the k-edge distance, denoted δ_k(s, v).

This leads to the concept of a witness to a negative weight cycle.

4. Bellman-Ford via Graph Duplication
The lecture presents a modified version of the Bellman-Ford algorithm that is easier to analyze. The core idea is to transform the original graph G into a new, larger graph G' that has a special structure.

5. The Bellman-Ford Algorithm
With this transformation, the SSSP problem on a general graph is reduced to an SSSP problem on a DAG, which we already know how to solve.

This algorithm successfully solves the SSSP problem for any directed graph, correctly identifying finite path weights, detecting negative weight cycles, and handling -∞ distances.

L19: Dijkstra

The lecture begins with a review of the algorithms for Single-Source Shortest Paths (SSSP) on weighted graphs covered so far:

2. Motivation for Dijkstra's Algorithm
The O(|V|*|E|) runtime of Bellman-Ford can be slow, especially for dense graphs (where |E| ≈ |V|^2, making the runtime O(|V|^3)). The lecture introduces a new, more efficient algorithm, Dijkstra's algorithm, for the common and important special case where all edge weights are non-negative.

3. Core Ideas and Observations
Dijkstra's algorithm is presented as a generalization of Breadth-First Search (BFS). BFS explores the graph in layers of increasing unweighted distance (number of edges). Dijkstra aims to do the same for weighted distance.

Dijkstra's algorithm combines these ideas: it will discover the correct ordering of vertices "on the fly" by always processing the "closest" unexplored vertex next.
4. The Changeable Priority Queue
To efficiently find the "closest" unexplored vertex at each step, Dijkstra's algorithm uses a special data structure called a changeable priority queue.

5. Dijkstra's Algorithm
The algorithm proceeds as follows:

  1. Initialization:
    • Initialize distance estimates d(s, v) = ∞ for all vertices v, and d(s, s) = 0.
    • Build a changeable priority queue containing all vertices. The priority (key) of each vertex v is its current distance estimate d(s, v). The ID is the vertex label itself.
  2. Main Loop: While the priority queue is not empty:
    • Extract the vertex u with the minimum distance estimate from the queue by calling delete_min(). The algorithm has now "finalized" the shortest path to u.
    • For each outgoing neighbor v of u:
      • Perform a relaxation step: If a shorter path to v has been found by going through u (i.e., if d(s, v) > d(s, u) + w(u, v)), then:
        • Update the distance estimate: d(s, v) = d(s, u) + w(u, v).
        • Update the priority queue by calling decrease_key(v, d(s, v)).

6. Correctness of Dijkstra's Algorithm
The correctness of the algorithm hinges on proving that whenever a vertex v is extracted from the priority queue, its distance estimate d(s, v) is equal to its true shortest path distance δ(s, v).

7. Runtime Analysis
The total runtime of Dijkstra's algorithm depends on the performance of the changeable priority queue used. The algorithm performs |V| delete_min operations and at most |E| decrease_key operations.

Priority Queue Implementation delete_min decrease_key Total Dijkstra Runtime Best for...
Unsorted Array O( V ) O(1)
Binary Heap O(log V ) O(log
Fibonacci Heap (Advanced) O(log V ) O(1) amortized

In practice and for the purposes of the course, the binary heap implementation is standard, giving a runtime of O(|E| log |V|). For dense graphs, the simple unsorted array implementation can actually be faster.

L20: PS 7

The session, led by instructor Justin Solomon, covers a series of graph theory problems. The problems focus on applying and modifying shortest path algorithms like Breadth-First Search (BFS) and Dijkstra's algorithm to solve non-standard problems.
Problem 1: Dijkstra's Algorithm Walkthrough
Goal: This is a mechanical problem designed to ensure a solid understanding of the step-by-step execution of Dijkstra's algorithm.

Problem 2: Weighted Radius
Goal: Compute the weighted radius of a directed graph with no negative weight cycles.

Problem 3: Atniss Keverdeen's Escape
Goal: In a labyrinth (a graph), find the shortest path from a source s to a target t that maximizes the minimum distance to any "sensor" vertex. The graph has a degree bound of 4.

Problem 4: Okepon Shipping
Goal: Design a data structure to handle shipping routes. Each route connects two cities, has a maximum weight capacity, and a cost. The final problem is to find the minimum cost to ship the heaviest possible server from a source s to a target t.

This two-phase approach first identifies the maximum possible flow (bottleneck) and then finds the cheapest way to achieve that flow.

L21: APSP and Johnson

The lecture begins with a review of the SSSP algorithms covered so far, emphasizing their runtimes and the types of graphs they apply to.

Algorithm Graph Type Runtime
BFS Unweighted `O(
DAG Relaxation DAGs (any weights) `O(
Bellman-Ford General (any weights) `O(
Dijkstra Non-negative weights `O(

The key takeaway is the performance gap between algorithms for graphs with non-negative weights (Dijkstra, near-linear) and general graphs that might have negative weights (Bellman-Ford, O(|V|*|E|), which is slow).
2. The All-Pairs Shortest Paths (APSP) Problem
The lecture introduces a new, more comprehensive problem.

3. A Naive Solution: Repeated SSSP
A simple, brute-force approach to solving APSP is to run an SSSP algorithm from every vertex.

The central question of the lecture is: Can we solve APSP on graphs with negative weights as efficiently as we can on graphs with only non-negative weights? The goal is to avoid the slow O(|V|^2 * |E|) runtime and get closer to the O(|V|*|E| + |V|^2 log |V|) runtime of repeated Dijkstra.
4. The Core Idea: Reweighting
The key idea behind Johnson's Algorithm is to transform the original graph G (which may have negative edge weights) into a new graph G' with the same vertices and edges, but with new, non-negative edge weights w', such that the shortest paths are preserved.

5. A Powerful Reweighting Technique
A specific reweighting technique is introduced that preserves shortest paths.

6. Finding the Right Potentials
The challenge is to find a set of potentials h(v) such that all the new edge weights w'(u, v) are non-negative.

7. Johnson's Algorithm
This insight leads to the full algorithm.

  1. Create a Super-Source: Since we don't have a single source that can necessarily reach all vertices, create a new "super-source" vertex s. Add a zero-weight directed edge from s to every other vertex in the graph. Let this new graph be G_s.
  2. Run Bellman-Ford Once: Run the Bellman-Ford SSSP algorithm from the super-source s on the graph G_s.
    • This takes O(|V|*|E|) time.
    • Cycle Detection: If Bellman-Ford detects a negative weight cycle (by finding a vertex v where δ(s, v) = -∞), then the original graph G must also have had a negative weight cycle (since s has no incoming edges). In this case, the algorithm aborts as required.
    • Compute Potentials: If there are no negative weight cycles, the shortest path distances δ(s, v) are now known for all v. These will be our potentials: set h(v) = δ(s, v).
  3. Reweight the Graph: Create the new graph G' by applying the reweighting formula to every edge in the original graph G using the potentials h(v) computed in the previous step. All edge weights in G' are now guaranteed to be non-negative.
  4. Run Dijkstra Now that G' has non-negative weights, run Dijkstra's algorithm from every vertex u on G' to compute all-pairs shortest path distances δ'(u, v). This takes O(|V| * (|E| + |V| log |V|)) time.
  5. Convert Distances Back: The distances δ'(u, v) computed in G' are not the same as the original distances. Use the path weight transformation formula to convert them back: δ(u, v) = δ'(u, v) - h(u) + h(v). This takes O(|V|^2) time.

Conclusion: Johnson's algorithm successfully solves the all-pairs shortest paths problem for graphs with negative weights (but no negative weight cycles) with a total runtime of O(|V|*|E| + |V|^2 log |V|). This is a significant improvement over running Bellman-Ford |V| times, effectively matching the efficiency of running Dijkstra on a graph that had non-negative weights to begin with.

L22: Quiz 2

Instructor Jason Ku begins by outlining the scope of the upcoming quiz.

2. General Problem-Solving Strategies for Graph Problems
Ku emphasizes a structured approach to solving graph problems, especially word problems.

  1. Modeling: The first and most critical step is to translate the word problem into a formal, abstract graph problem.
    • Explicitly define the graph: What are the vertices? What are the edges? Are they weighted? Directed?
    • State the problem to be solved on that graph (e.g., "This corresponds to finding the SSSP from vertex p in the constructed graph G.").
  2. Graph Transformation: Often, the initial graph model needs to be modified to fit a known algorithm. Common "tricks" include:
    • Graph Duplication / State Expansion: Create multiple copies of vertices to store state information (e.g., (vertex, state) becomes a new vertex).
    • Adding an Auxiliary Vertex (Supernode): Add a new source or sink node with edges to multiple other nodes to solve multi-source or multi-sink problems with a single SSSP run.
    • Preprocessing/Filtering: Remove or modify vertices/edges that don't satisfy certain conditions to simplify the problem (e.g., removing edges to create a DAG).
  3. Correctness and Runtime Analysis:
    • Correctness: The main argument is linking the solution on the transformed graph back to the original word problem. The correctness of the underlying algorithms (like BFS, Dijkstra) can usually be assumed.
    • Runtime: Analyze the size (|V'| and |E'|) of the transformed graph and apply the known runtime of the chosen algorithm to that new size. Forgetting to account for the increased size of the graph is a common mistake.

Problem 1: Counting Blobs
Goal: Given an N x M grid of black and white pixels, count the number of "blobs" (connected components of white pixels) in linear time (O(N*M)).

Problem 2: Tree Plus One Edge
Goal: Given a connected, undirected graph with positive edge weights and the special property that |E| = |V|, find the shortest path from s to t in O(|V|) time.

Problem 3: Donut Danger
Goal: Find the shortest path from a power plant P to home H in a city, with the constraint that the path must never come within a driving distance k of any donut shop.

Problem 4: Long Shortest Paths
Goal: In a directed graph with arbitrary edge weights, find the minimum weight of any path from s to t that contains at least .

L23: Dynamic Programming 1

The lecture introduces a new unit focused on algorithmic design, shifting from using and analyzing existing algorithms to creating new ones from scratch. The central paradigm for this unit is dynamic programming (DP).

2. SRTBOT: A Framework for Recursive Algorithms
A six-step framework called SRTBOT is introduced as a systematic way to design and describe recursive algorithms.

  1. S - Subproblems: Define the set of subproblems that need to be solved. The goal is to have a polynomial number of them.
  2. R - Relate: Establish a recursive relationship (a recurrence) that shows how to solve a larger subproblem using the solutions to one or more smaller subproblems.
  3. T - Topological Order: Define a valid ordering in which to solve the subproblems to ensure that when solving a problem, the solutions to its required subproblems have already been computed. This guarantees the recursion is acyclic and terminates.
  4. B - Base Cases: Specify the smallest subproblems that can be solved directly, without recursion.
  5. O - Original Problem: Show how to solve the original problem using the defined subproblems (often, the original problem is just one of the subproblems).
  6. T - Time: Analyze the overall running time of the algorithm.

Merge sort is presented as a familiar example fitting the SRTBOT framework.
3. The Problem with Naive Recursion: Fibonacci Numbers
The Fibonacci sequence is used as a toy problem to illustrate a critical flaw in naive recursive algorithms.

4. Memoization: The Core Idea of Dynamic Programming
The solution to the inefficiency of naive recursion is memoization. This is the key idea that elevates a simple recursive algorithm into a dynamic programming solution.

5. Bottom-Up DP vs. Top-Down (Memoized) DP
Two equivalent ways of implementing a DP solution are presented:

  1. Top-Down with Memoization (as described above): A recursive function that checks a memo table. It starts with the original problem and recursively breaks it down.
  2. Bottom-Up DP: An iterative approach that uses the topological order.
    • Algorithm:
      1. Create a table to store the solutions.
      2. Initialize the base cases in the table.
      3. Use a for loop that iterates through the subproblems in their topological order.
      4. Inside the loop, use the recurrence relation to fill in the table entry for the current subproblem, using values that have already been computed.
    • This approach avoids recursion overhead and is often more efficient in practice.

6. Revisiting DAG Shortest Paths as a DP
The SSSP on a DAG problem is reframed as a dynamic program, demonstrating its connection to the DP paradigm.

It's noted that a memoized recursive implementation of this DP is essentially a Depth-First Search on the (reversed) subproblem graph.
7. New Problem: The Bowling Game
A new problem is introduced to demonstrate how to design a DP from scratch.

L24: Dynamic Programming 2

The lecture begins with a review of the dynamic programming (DP) paradigm, presented as a general framework for designing polynomial-time algorithms from scratch.

2. Problem 1: Longest Common Subsequence (LCS)
Goal: Given two sequences, A and B, find the longest sequence L that is a subsequence of both.

3. Problem 2: Longest Increasing Subsequence (LIS)
Goal: Given a single sequence A of numbers, find the longest subsequence that is strictly increasing.

4. Problem 3: Alternating Coin Game
Goal: Two players take turns picking a coin from either the left or right end of a line of coins. Find the maximum total value the first player can guarantee for themselves, assuming the second player also plays optimally.

L25: PS 8

Instructor Justin Solomon begins by reviewing the core concepts of dynamic programming (DP), framing it as a problem-solving approach rather than a single specific algorithm.

2. Problem 1: Tim the Beaver's Happiness
Goal: Tim the Beaver has n days until an exam. On day i, the temperature is t_i. Tim can either go outside (happiness increases by t_i) or stay inside (happiness is unchanged). The constraint is that Tim can never go outside for more than two consecutive days. The task is to find a plan that maximizes Tim's total happiness.

3. Problem 2: Menix Edit Distance
Goal: Find the minimum number of "expensive" operations (adding or deleting a line) to transform file A into file B. "Cheap" operations (swapping adjacent lines) are also allowed and are free, but only if the swap is "useful" (i.e., it helps create a match).

4. Problem 3: Saggy Mimsin's Blocks
Goal: Given n types of rectangular blocks, find the tallest possible tower that can be built by stacking them. A block can only be placed on another if its base is strictly smaller in both dimensions than the block below it. Blocks can be rotated.

5. Problem 4: Princess Plum's Mushrooms
Goal: In an n x n grid with mushrooms and trees, find the number of "quick" paths from (1,1) to (n,n) that collect the maximum possible number of mushrooms. A quick path is one that takes exactly 2n-2 steps.

L25: Dynamic Programming 3

The lecture begins by contextualizing the ongoing unit on dynamic programming (DP), a powerful and general paradigm for algorithm design. The instructor, Erik Demaine, briefly touches on the history of the term "dynamic programming," noting it was coined by Richard Bellman in the 1950s.

2. SSSP as a Dynamic Program
The lecture reframes two Single-Source Shortest Path (SSSP) algorithms as dynamic programs to illustrate the power of the DP framework.
SSSP on a DAG

Bellman-Ford

3. All-Pairs Shortest Paths (Floyd-Warshall)
A new O(|V|^3) algorithm for APSP is introduced as another example of non-obvious subproblem expansion.

4. Problem 1: Arithmetic Parenthesization
Goal: Given an arithmetic expression with numbers and operators (+, *), find a parenthesization that maximizes its value. The numbers can be negative.

5. Problem 2: Piano/Guitar Fingering
Goal: Given a sequence of musical notes, assign a finger (1-5) to each note to minimize the total "difficulty." The difficulty is defined by a function d(note1, finger1, note2, finger2) which penalizes awkward transitions.

The lecture concludes by showing how this DP framework can be generalized to more complex musical instruments like guitar (where you choose both a finger and a string) and even to video games like Rock Band, demonstrating the power of subproblem expansion to capture additional state.

L26: Dynamic Programming 4

The lecture, led by Erik Demaine, is the final installment in the four-part series on dynamic programming (DP). It focuses on a specific type of DP where the subproblems are defined by integers.

2. Problem 1: Rod Cutting
Goal: Given a rod of integer length L and a table of values v(l) for selling a piece of length l, find a partition of the rod into smaller integer-length pieces that maximizes the total value.

3. Polynomial vs. Pseudo-Polynomial Time
This section introduces a crucial distinction in runtime analysis.

4. Problem 2: Subset Sum
Goal: Given a multiset A of n positive integers and a target integer T, determine if there exists a subset of A whose elements sum to exactly T.

5. Review of All DP Examples
The lecture concludes with a comprehensive review of all the DP examples covered in the four-lecture series, categorizing them by the key techniques used. This provides a high-level map of the DP landscape.

This systematic review ties all the examples together, showing how a few core principles (subproblem definition, relation formulation, expansion) can be applied in various combinations to solve a wide range of complex problems.

L27: Complexity

The lecture shifts focus from algorithms (finding efficient solutions to problems) to computational complexity (proving that problems are inherently difficult and that efficient algorithms likely do not exist).

2. Complexity Classes: P, EXP, and R
Several fundamental complexity classes are defined to categorize problems based on their difficulty.

3. Uncomputable Problems and the Halting Problem
The lecture makes a striking claim: most problems are not just hard, but are fundamentally unsolvable.

4. The Class NP: "Lucky" Algorithms and Verification
The most famous complexity class, NP, is introduced. It lies between P and EXP.

5. P vs. NP and NP-Hardness

6. The Landscape of NP-Completeness and Examples

L28: PS 9

Instructor Justin Solomon begins by framing the session as a continuation of dynamic programming, focusing on problems where the runtime depends not just on the number of items (n) but also on the magnitude of the numbers involved.

2. Problem 1: Coin-Crafting (Knapsack Problem)
Goal: A thief has n identical coins and can craft any of n unique objects. Object i has a price p_i and requires k_i coins to make. The thief wants to choose a set of objects to craft to maximize total revenue, given the constraint of having only n coins.

3. Problem 2: Tim the Beaver's Career Fair (Unbounded Knapsack)
Goal: Tim the Beaver visits a career fair with n booths. Booth i gives swag with coolness c_i and weight w_i, and has a wait time of t_i. Tim can visit any booth multiple times. He has k total minutes and a bag that can hold a total weight of b. He can also spend h minutes to go home, empty his bag, and return. Find the maximum total coolness Tim can collect.

4. Problem 3: Protein Parsing
Goal: Given a DNA string S and a dictionary P of "marker" substrings, find a partition of S into smaller substrings that maximizes the number of substrings that are in P.

5. Problem 4: Lazy Egg Drop
Goal: In a building with n floors, you have k identical eggs. Find an optimal strategy for dropping eggs to determine the critical floor (the lowest floor from which an egg breaks) that minimizes the total height dropped in the worst case.

The session concludes by setting up a fifth problem but running out of time to solve it in detail.

L29: Quiz 3

Instructor Jason Ku begins by outlining the scope and focus of the upcoming quiz.

2. The SRTBOT Framework Review
The six-step SRTBOT framework for designing and presenting DP solutions is reviewed in detail.

  1. S - Subproblems: Define what each subproblem computes and how it is parameterized. This is the most crucial step. Common strategies include using prefixes, suffixes, or substrings for sequence inputs, and multiplying subproblem spaces for multiple inputs. Subproblems can also be expanded with extra parameters to "remember" state.
  2. R - Relate: Write a precise mathematical recurrence relation showing how to solve a subproblem using the solutions to smaller ones. The general strategy is to identify a "question" about the solution and use local brute force (e.g., a max, min, or OR) over all possible answers.
  3. T - Topological Order: Argue that the subproblem dependencies are acyclic, usually by showing that some parameter consistently increases or decreases.
  4. B - Base Cases: Define the smallest subproblems that can be solved non-recursively. This is essential for the recursion to terminate.
  5. O - Original Problem: State how the final answer is derived from the subproblems. This may also involve using parent pointers to reconstruct the actual solution (e.g., the sequence of choices) and not just the optimal value.
  6. T - Time: Analyze the runtime, typically calculated as (Number of Subproblems) * (Non-recursive work per subproblem).

3. Problem 1: Lottery Winnings
Goal: Given a list of lottery payouts for the next n days, find the maximum winnings possible with the constraint that you can play at most twice in any seven-day period.

4. Problem 2: DNA Ancestry (LCS Variant)
Goal: Given three DNA sequences of length n (Alice, Bob, Charlie), determine if Charlie's DNA can be partitioned into two equal-length subsequences, one of which is a subsequence of Alice's DNA and the other a subsequence of Bob's.

5. Problem 3: Tapas (0/1 Knapsack with a Twist)
Goal: Given n tapas plates, each with a volume, calorie count, and a sweetness label (0 or 1), find the maximum total volume of food one can eat without exceeding k calories, while eating exactly s sweet plates. Each plate can be chosen at most once.

L30: Course Review

The lecture, led by instructor Jason Ku, serves as a high-level review of the entire course and a look forward to more advanced topics. It begins by recapping the three primary goals of 6.006:

  1. Solve Computational Problems: Develop algorithms to find solutions.
  2. Argue Correctness: Prove that the algorithms work correctly for all valid inputs. This is framed as an application of the principles learned in discrete mathematics (6.042).
  3. Argue Efficiency ("Goodness"): Analyze the performance of algorithms, especially how they scale with input size. This relies on the course's model of computation and asymptotic analysis.
  4. Communication: A fourth, overarching goal is emphasized: the ability to clearly communicate these algorithms and arguments to other humans, not just to a computer.

2. The Big Picture: A Map of the Course Content
The material covered in the course is broken down into three main units, corresponding to the three quizzes.
Unit 1: Foundations and Data Structures (Quiz 1)

Unit 2: Graph Algorithms (Quiz 2)

Unit 3: Algorithmic Design (Quiz 3)

3. A Look Ahead: 6.046 (Design and Analysis of Algorithms)
The lecture positions the next course in the theory sequence, 6.046, as a natural continuation of 6.006. While 6.006 focuses on using and understanding a core set of algorithms, 6.046 delves deeper into the design and, more significantly, the analysis of new and more complex algorithms.

4. Beyond the 6.006 Framework: New Paradigms
The final part of the lecture explores topics beyond the standard 6.006/6.046 curriculum, where the fundamental assumptions of the course are relaxed or changed.

The lecture concludes by framing 6.006 as a foundational course that provides the essential tools and thinking patterns for tackling a wide array of problems in computer science and beyond.

L31: Algorithms, next steps…

The final lecture begins with instructor Jason Ku reflecting on the broader context of the course and its connection to real-world innovation.

Part 1 by Erik Demaine: Computational Geometry, Folding, and Data Structures
Professor Demaine presents a whirlwind tour of his research, demonstrating the deep connection between algorithms, geometry, and art.

Part 2 by Justin Solomon: Applied Geometry in Graphics and Beyond
Professor Solomon discusses his research, which applies geometric and algorithmic principles to practical problems in computer graphics, machine learning, and even public policy.

Conclusion and Farewell
The lecture concludes with all three instructors sharing a final message. They emphasize that the principles of algorithms, data structures, and computational thinking are not just academic exercises but are truly foundational, appearing in every corner of computer science and beyond. They thank the students for taking the course and wish them well.

All writing