Computational Thinking — Overview
Phase 0 -- Computational Thinking (Days 1-3)
Most courses skip this. This is why most learners fail. Before you learn ANY data structure or algorithm, you need to learn how to THINK about problems.
Deep Dive: See the Problem Before Coding
The Maze is a Graph. The Sudoku is Backtracking. You Already Know This.
Before you learn a single algorithm, realize you have been solving DSA problems your whole life:
Problem States Transitions Goal
--- --- --- ---
Navigate a city Intersections Roads Reach destination
Solve a jigsaw puzzle Partial placements Place next piece Complete image
Make change for $1 Remaining amount Subtract a coin Reach $0
Schedule meetings Possible orderings Assign a time slot No conflicts
Each of these maps directly to a data structure + algorithm:
Navigate a city -> Graph + BFS (shortest path)
Jigsaw puzzle -> Backtracking (try, fail, undo)
Make change -> DP (overlapping subproblems)
Schedule meetings -> Greedy / Intervals (sort + sweep)
You are not "learning algorithms." You are learning to SEE that everyday problems are states + transitions, then CHOOSING the right tool.
Module 0.1: What Is a Problem?
Every problem in DSA (and in life) has three parts:
Problem = (States, Transitions, Goal)
- States = Where you can be (all possible situations)
- Transitions = How you move between states (the rules)
- Goal = What you are trying to reach (the answer)
Think of it like a GPS: you have a starting location (state), roads you can take (transitions), and a destination (goal). DSA is just choosing the fastest route.
Exercise: See the Pattern in Everyday Problems
Maze: Your state is (row, col). You can move up/down/left/right. Goal = reach the exit.
-> This is a graph problem. BFS finds the shortest path. You don't memorize this -- you SEE it.
Sudoku: State = current grid. Transitions = place a number. Goal = all rows/cols/boxes valid. -> This IS backtracking. You try, validate, undo if wrong.
Coin change: State = remaining amount. Transitions = subtract a coin. Goal = reach 0. -> This IS DP. Many paths overlap.
Key Mindset Shift
"I'm not learning data structures and algorithms. I'm learning to SEE that every problem is just states and transitions, and then CHOOSING the right tool to explore them efficiently."
- I can decompose a real-world problem into States, Transitions, Goals
Module 0.2: Big-O Intuition
Why Care?
Because the difference between O(n) and O(n^2) is the difference between "runs in 1 second" and "runs for 11 days" when n = 10^6.
| n | O(1) | O(log n) | O(n) | O(n log n) | O(n^2) | O(2^n) |
|---|---|---|---|---|---|---|
| 10 | 1 | 3 | 10 | 33 | 100 | 1,024 |
| 100 | 1 | 7 | 100 | 664 | 10K | 1.2x10^30 |
| 10^6 | 1 | 20 | 10^6 | 20M | 10^12 | INF |
The Golden Rule
DSA = Techniques to REDUCE the Search Space
Every data structure and algorithm exists for one reason: brute force is too slow. If checking every possibility were fast enough, we would not need DSA.
For example: finding a name in an unsorted list of 1 million names takes 1 million checks (brute force). Sorting first, then binary searching, takes only 20 checks. That is the power of DSA.
How to Estimate Complexity
A modern computer does ~10^8 operations/second. Your solution must finish in ~1-2 seconds.
- n = 10^6 -> you need O(n) or O(n log n). O(n^2) = 10^12 operations = too slow.
- n = 10^4 -> O(n^2) = 10^8 = borderline OK.
- n = 20 -> O(2^n) = 10^6 = fine.
Exercise
For each, say "too slow" or "OK":
-
n = 10^5, your solution is O(n^2) -> ?
-
n = 500, your solution is O(n^3) -> ?
-
n = 15, your solution is O(2^n) -> ?
-
n = 10^6, your solution is O(n log n) -> ?
-
I can look at constraints and immediately know the target complexity
Module 0.3: Constraint -> Complexity Mapping
This is what interviewers ACTUALLY test. They don't ask "what's the complexity of merge sort?" -- they give you a problem with constraints and want you to pick the right approach.
| Constraint (n <=) | Expected Complexity | What This Tells You |
|---|---|---|
| 10 | O(n!) or O(2^n) | Brute force / backtracking is fine. Try all possibilities. |
| 20-25 | O(2^n) | Bitmask DP or Meet in Middle. Small enough for exponential. |
| 100 | O(n^3) | Three nested loops or Floyd-Warshall OK. |
| 1,000 | O(n^2) | DP with 2D table, or optimized brute force. |
| 10^5 | O(n log n) | Sort first, then binary search / two pointers. |
| 10^6 | O(n) | Single pass. Two pointers, prefix sum, or HashMap. |
| 10^9+ | O(log n) or O(1) | Math trick or binary search on answer. |
The Protocol
ALWAYS read constraints before thinking about approach. This is your first filter.
1. Read n <= 10^5
2. Think: "I need O(n log n) or O(n)"
3. This eliminates: brute force O(n^2), triple loops, all-pairs
4. This suggests: sorting, binary search, two pointers, sliding window, HashMap
5. NOW think about which one fits the problem
- I read constraints FIRST and use them to eliminate approaches
Module 0.4: Recursion as a Thinking Framework
Why Recursion Matters
Recursion isn't just a technique -- it's a way of thinking. Almost every advanced pattern (DFS, backtracking, DP, divide & conquer) is recursive at its core.
The Mental Model: TRUST THE FUNCTION
1. Define what f(x) does in ONE clear sentence
2. Assume f(smaller_x) ALREADY WORKS (the Leap of Faith)
3. Build f(x) using f(smaller_x)
4. Handle the base case (where f stops calling itself)
Example: Sum of a list
What does sum(arr) do? -> Returns the sum of all elements
Leap of Faith: sum(arr[1:]) already returns the sum of everything except arr[0]
Build: sum(arr) = arr[0] + sum(arr[1:])
Base case: sum([]) = 0
Example: Fibonacci
What does fib(n) do? -> Returns the nth Fibonacci number
Leap of Faith: fib(n-1) and fib(n-2) already work
Build: fib(n) = fib(n-1) + fib(n-2)
Base case: fib(0) = 0, fib(1) = 1
The Recursion Tree Visualization — fib(5) (small example)
fib(5)
|-- fib(4)
| |-- fib(3)
| | |-- fib(2) <-- REPEATED
| | '-- fib(1)
| '-- fib(2) <-- REPEATED
'-- fib(3) <-- REPEATED
|-- fib(2) <-- REPEATED
'-- fib(1)
The Same Tree for fib(6) — Watch the Explosion
Below is a visual diagram of the same call tree. Each duplicated node (orange) is recomputed every time we visit it — memoization eliminates exactly these duplicates.
graph TD F6["fib(6)"] --> F5a["fib(5)"] F6 --> F4a["fib(4)"] F5a --> F4b["fib(4)"] F5a --> F3a["fib(3)"] F4a --> F3b["fib(3)"] F4a --> F2a["fib(2)"] F4b --> F3c["fib(3)"] F4b --> F2b["fib(2)"] F3a --> F2c["fib(2)"] F3a --> F1a["fib(1)"] F3b --> F2d["fib(2)"] F3b --> F1b["fib(1)"] F3c --> F2e["fib(2)"] F3c --> F1c["fib(1)"] F2a --> F1d["fib(1)"] F2a --> F0a["fib(0)"] F2b --> F1e["fib(1)"] F2b --> F0b["fib(0)"] F2c --> F1f["fib(1)"] F2c --> F0c["fib(0)"] F2d --> F1g["fib(1)"] F2d --> F0d["fib(0)"] F2e --> F1h["fib(1)"] F2e --> F0e["fib(0)"] classDef dup fill:#fef3c7,stroke:#f59e0b,color:#92400e class F4b,F3a,F3b,F3c,F2a,F2b,F2c,F2d,F2e dup
fib(6)
|-- fib(5)
| |-- fib(4)
| | |-- fib(3)
| | | |-- fib(2) <-- f(2) call #1
| | | | |-- fib(1)
| | | | '-- fib(0)
| | | '-- fib(1)
| | '-- fib(2) <-- f(2) call #2
| | |-- fib(1)
| | '-- fib(0)
| '-- fib(3)
| |-- fib(2) <-- f(2) call #3
| | |-- fib(1)
| | '-- fib(0)
| '-- fib(1)
'-- fib(4)
|-- fib(3)
| |-- fib(2) <-- f(2) call #4
| | |-- fib(1)
| | '-- fib(0)
| '-- fib(1)
'-- fib(2) <-- f(2) call #5
|-- fib(1)
'-- fib(0)
Count the calls: fib(2) is computed 5 times. fib(3) is computed 3 times. fib(4) is computed 2 times. For fib(40), this is ~330 million calls. For fib(50), ~40 billion.
This visceral cost is exactly why memoization (and the entire DP module in Phase 2) exists. Cache the answer for each fib(k) the first time you compute it, and you cut the same problem to 50 calls.
See the repeated work? This is WHY memoization (DP) exists.
Key Recursion Patterns
| Pattern | What It Looks Like | Example |
|---|---|---|
| Linear | f(n) calls f(n-1) once | Factorial, linked list traversal |
| Binary | f(n) calls f(left) + f(right) | Tree traversal, merge sort |
| Multiple | f(n) calls f(choice1), f(choice2)... | Permutations, backtracking |
| Tail | The recursive call is the LAST operation | Optimized factorial |
Exercises
Three short exercises. Each has a starter file in exercises/ (write your code there) and a fully-worked solution in solutions/ (peek only after you've tried for 15 minutes).
reverse_string(s)— reverse a string using recursion. Hint: first char + reverse of rest. Starter:exercises/01-reverse-string.py· Solution:solutions/01-reverse-string.pycount_nodes(tree)— count nodes in a binary tree. Hint: 1 + count(left) + count(right). Starter:exercises/02-count-nodes.py· Solution:solutions/02-count-nodes.pyfib(n)— write the naive recursion, then memoize it. Goal: feel the speedup yourself. Starter:exercises/03-fib.py· Solution:solutions/03-fib.py
After you finish, draw the recursion tree for fib(6) by hand — count how many times fib(2) is computed. Match your count against the diagram above.
- I can write a recursive solution by trusting the function
- I can draw a recursion tree and spot repeated work
Python Gotchas Every Beginner Hits
Three Python quirks that consistently trip people up in early DSA problems. Read these once now and save yourself an hour of debugging.
1. The Recursion Limit
Python caps recursion depth at 1000 calls by default. Your naive fib(40) will hit it. Bump it for problems that need deep recursion (e.g. linked lists with 10,000+ nodes):
import sys
sys.setrecursionlimit(10**6)
In an interview, mention this once. Then either bump the limit or convert to an iterative solution — both are fine answers.
2. Mutable Default Arguments
Python evaluates default arguments once when the function is defined, not each time it's called. This breaks memoization-via-default-arg in subtle ways:
# WRONG — the cache persists across all top-level calls!
def fib(n, memo={}):
if n in memo: return memo[n]
...
# Two separate problem instances will pollute each other's cache.
# RIGHT — pass the cache explicitly, or build it inside.
def fib(n):
memo = {}
def helper(k):
if k in memo: return memo[k]
...
return helper(n)
3. Integer Division: // vs /
/ always returns a float in Python 3. // returns an int (floor division). Mid-array binary search must use //:
mid = (lo + hi) / 2 # WRONG — produces 2.5, can't index list with float
mid = (lo + hi) // 2 # RIGHT — produces 2
For negative numbers, // rounds toward negative infinity, not toward zero — (-7) // 2 == -4, not -3. Use int(a / b) if you need C-style truncation.
Before Moving to Phase 1
You should be able to answer YES to all of these:
- I can break any problem into States, Transitions, and Goals
- I can look at
n <= Xand immediately know the target time complexity - I always think of brute force first, then ask "why is this slow?"
- I understand recursion well enough to trust the function
- I know that repeated work in recursion -> DP, and I can spot it in a tree
External Resources (Hand-Picked Supplements)
When the README explanation doesn't click, try these — every link is free.
- Harvard CS50P — Week 5 (Recursion) — David Malan's recursion intro with visual call-stack animations. The most beginner-accessible recursion explanation on the web.
- freeCodeCamp — Big-O Notation in 30 Minutes — companion piece for Module 0.2; uses concrete code samples to anchor each complexity class.
- Dan Bader — Why Mutable Default Arguments Are Tricky — read once, never get bitten again.
- Visualgo — Recursion — animated recursion-tree explorer. Step through
fib(6)to see the explosion in real time.