Jed Rembold
December 3, 2025
3198345-61d515c2Computing an element of the Fibonacci sequence is a classic recursive problem that can be solved by:
def fibonacci(n):
if n <= 1:
return 1
else:
return fibonacci(n-1) + fibonacci(n-2)
How many times (including the first) does the function get called
when calling fibonacci(5)?
The plan with merge sort is thus to:
a1 and
a2a1 and
a2 recursively, with a base case when the
list has a single elementa1 or
a2 on each cycleWe’ll follow this process all the way through on pen and paper now to see that it works!
def merge_sort(array):
if len(array) > 1: #base case check
mid = len(array) // 2
a1 = array[:mid]
a2 = array[mid:]
merge_sort(a1) #recursive calls
merge_sort(a2)
merge(array, a1, a2)
def merge(array, a1, a2):
n1 = len(a1)
n2 = len(a2)
i1 = 0 #current front indices
i2 = 0
for i in range(len(array)):
if (i1 < n1 and a1[i1] < a2[i2]) or i2 == n2:
array[i] = a1[i1]
i1 += 1
else:
array[i] = a2[i2]
i2 += 1
| \(N\) | \(N^2\) | \(N\log_2 N\) |
|---|---|---|
| 10 | 100 | 33 |
| 100 | 10,000 | 664 |
| 1,000 | 1,000,000 | 9,966 |
| 10,000 | 100,000,000 | 132,877 |
| 100,000 | 10,000,000,000 | 1,660,964 |
| 1,000,000 | 1,000,000,000,000 | 19,931,569 |
| Name | Big-O | Example |
|---|---|---|
| constant | \(\mathcal{O}(1)\) | Finding the first element of an array |
| logarithmic | \(\mathcal{O}(\log N)\) | Binary search in a sorted array |
| linear | \(\mathcal{O}(N)\) | Summing over an array, or linear search |
| \(N \log N\) | \(\mathcal{O}(N\log N)\) | Merge sort |
| quadratic | \(\mathcal{O}(N^2)\) | Selection sort |
| cubic | \(\mathcal{O}(N^3)\) | Obvious algorithms for matrix multiplication |
| exponential | \(\mathcal{O}(2^N)\) | Branch and try all possibilities |
Python is a dynamically typed language: we can change around what type of object is assigned to different variables whenever we want
We can indicate these types by using type hints or type annotations
def greet(name: str) -> str:
return f"Hello, {name}!")You can specify what type a variable should have by including the type after a colon when you declare the variable name
user_name: str = "Jed"
age: int = 40
is_living: bool = TrueIf you have compound structures, you can indicate the contents:
profs: list[str] = ['Jed', 'Calvin', 'Fred']
prof_start: tuple[str, int] = ('Jed', 2015)
profs_start: list[tuple[str, int]] = [
('Jed', 2015), ('Calvin', 2022)
]
prof_dict: dict[str, int] = {'Jed': 2025}Even more than variable annotations, function annotations can be extremely useful to define input and output types
def myfunc(x: int, y: float, z: bool = True) -> float:
if z:
return x * y
else:
return x / yHints come before any default values
Use the -> |||type||| syntax to
specify what will be returned
Sometimes, you do want to allow for situations where a variable might have multiple possible types
NoneYou can used the | symbol to indicate
multiple variable types:
def index_finder(
array: list[str],
target: str
) -> int | None:
for i, elem in enumerate(array):
if elem = target:
return i