Principles of Computer
Programming II
Week - 3
Irakli Iremashvili
• Combinations 🧩
o Selecting k elements from n (where order
doesn't matter)
o Recursive implementation with
include/exclude
o Supreme Court and team selection examples
• Decision Problems 🤔
o Moving from "list all solutions" to "is there a
solution?"
Today's o Shrinkable words problem
o Early returns and efficiency
Agenda📋️ • Recursive Backtracking 🔍
o Trying multiple paths and backtracking when
necessary
o Using Optional<T> to handle success/failure
o ChEMoWIZrDy problem: spelling with element
symbols
• Big-O Notation 📈
o Understanding algorithm efficiency
o Analyzing code examples
o Real-world applications and scaling
o Time complexity of recursive algorithms
Part 1:
Combinations &
Decision Problems
So far we've explored:
• Recursive structures (trees, fractals)
• The recursive leap of faith
Quick Recap: • Subsets: Include/exclude each element
• Permutations: Place elements in
Where We've different orders
Been 🔄 Today we'll explore:
• Combinations: Selecting k elements
from n
• Decision Problems: "Is there a way
to...?"
• Definition: Ways to select k elements
from a set of n elements, where order
doesn't matter
• Example: Selecting 3 students from a
class of 5
What are • Formula: C(n,k) = n! / (k! × (n-k)!)
How is this different from subsets and
Combinations? permutations?
🧩 • Subsets: Can select any number of
elements (0 to n)
• Permutations: The order matters
• Combinations: Select exactly k
elements, order doesn't matter
Example: Selecting 2 people from {"Alice",
"Bob", "Charlie"}
• Combinations (k=2):
• {Alice, Bob}
• {Alice, Charlie}
• {Bob, Charlie}
Combinations: Total: 3 combinations
Examples 📊
Real-world applications:
• Selecting 5 Supreme Court justices from
9
• Choosing a team of 3 from 10 students
• Picking lottery numbers (5 from 49)
We can use a similar include/exclude
approach as with subsets:
Combinations: • For each element:
Recursive •
•
Include it and select (k-1) more elements
Exclude it and select k more elements
Approach 🧠 • Base cases:
• When k=0 (need 0 more elements): return
current selection
• When k>elements remaining: return empty set
(impossible)
Combinations: C++
Implementation 💻
Tracing Combinations 🔍
Example: combinations({"A", "B", "C"}, 2)
• Initial call: combinationsRec({"A", "B",
"C"}, 2, {})
• Element A:
• Exclude A: combinationsRec({"B",
"C"}, 2, {})
• Returns: {{"B", "C"}}
• Include A: combinationsRec({"B",
"C"}, 1, {"A"})
• Returns: {{"A", "B"}, {"A", "C"}}
• Result: {{"B", "C"}, {"A", "B"}, {"A", "C"}}
So far, we've asked "list all ways to..."
questions:
• List all subsets
• List all permutations
• List all combinations
Transition to
Decision Now let's shift to "is there a way to...?"
questions:
Problems 🤔 • Is there a way to reach a target sum?
• Is there a valid solution to this puzzle?
• Can this word be transformed in a
specific way?
This approach focuses on existence rather
than enumeration.
Decision Problem: A word is "shrinkable"
Problems: You can remove
Each if:
resulting
word is valid (in
You eventually
reach a single-
one letter at a time
Shrinkable the dictionary) letter word
Words 📝
Example: "STRING" → "STING" →
"SING" → "SIN" → "IN" → "I"
Is "STRING" shrinkable? Yes Is
"APPLE" shrinkable? Let's find
out...
STRIN
STING SING SIN IN I
G
1. Base cases:
Shrinkable •
•
If the word isn't in the dictionary: false
If the word has just one letter: true
Words: 2. Recursive case:
Recursive •
•
For each letter in the word, try removing it
If any resulting word is shrinkable, return true
Approach 🧠 • If all options fail, return false
Shrinkable
Words: C++
Implementation
💻
The decision tree for
"SING":
• Remove 'S': "ING" (try
this path first)
• Remove 'I': "SNG" (try
if first path fails)
• Remove 'N': "SIG" (try
if first two paths fail)
• Remove 'G': "SIN" (try
if first three paths fail)
Compare: Enumeration vs. Decision
Problems 📊
Aspect Enumeration Decision
Goal List all solutions Find if a solution
exists
Return type Collection Boolean
When to return After generating As soon as one
all solutions solution is found
Recursion Try all paths Try paths until
pattern success
Example List all Check if word is
combinations shrinkable
1. Combinations:
• Select k elements from n where order
doesn't matter
• Use include/exclude strategy similar
to subsets
• Need to track how many more
Key Takeaways: 2.
elements to select
Decision Problems:
Combinations & • Focus on existence rather than
enumeration
Decision • Return as soon as a solution is found
Problems 🔑 • Can often be more efficient than
listing all solutions
3. Problem Solving Structure:
• Clear base cases (success and failure)
• Systematic exploration of all options
• Early returns when a solution is found
Part 2:
Recursive
Backtracking
Warm-Up: Spot the
• What's wrong with this function?
Bug 🐞
Warm-Up: Spot the • The bug: It returns after checking only the
Bug 🐞 first character!
• Definition: A technique where
we explore all possible
solutions by making a
What is sequence of choices, and if
Recursive one path fails, we "backtrack"
and try a different option.
Backtracking? 🔄 • Key Idea: Don't give up after
one failure - try all
possibilities!
Common Backtracking Pattern 📝
1. Maze solving
• Try each direction; if it leads
to a dead end, backtrack
2. N-Queens
• Place queens so none attack
Examples of each other
Backtracking 3. Sudoku solving
• Try digits in empty cells;
Problems 🧩 backtrack if constraints are
violated
4. Word puzzles
• Finding words in a grid
• Breaking cryptograms
• Spell checking
Tenacity in
Recursion ⚡
• Consider our
shrinkable words
function again:
• Backtracking: If
removing the first
letter fails, we try the
second, then the
third, and so on.
Common
Backtracking Bug: • Bug: Returns the result from the first attempt
Giving Up Too Soon without trying other options!
⚠️
When to Use • Rule of thumb: Inside a loop, only use
Returns in Loops return with an if condition:
🤔
Problem: How do we handle the
case when a recursive function
might not find a solution?
Options:
Introducing • Return a special value (but what if
all values are valid?)
Optional<T> • Return a bool and modify a
📦 parameter (messy)
• Use global variables (even
messier)
• Use Optional<T> - represents "a
value or nothing"
How
Optional<
T>
Works 📦
Problem: Determine if a word can
be spelled using only element
symbols from the periodic table.
Examples:
• "CARBON" → "C" + "Ar" + "B" +
ChEMoWIZrDy "O" + "N" ✓
Problem 🧪 • "BACON" → "B" + "Ac" + "O" +
"N" ✓
• "WIZARD" → ? Let's find out!
Approach: Try all possible ways
to decompose the word into
element symbols.
1. Start at the beginning of the word
2. Try using 1 or 2 letters as an element
symbol
3. If it's a valid element, recursively try
to spell the rest of the word
ChEMoWIZrDy: 4. If any decomposition works, the word
is spellable
Breaking Down Example: "BACON"
• "B" is an element, try "ACON"
the Problem 🔍 • "Ac" is an element, try "ON"
• "O" is an element, try "N"
• "N" is an element, try "" (empty string)
• Empty string is spellable by definition
• Success! B+Ac+O+N
What should our function return?
• bool - Just whether the word is
spellable
• string - The element
ChEMoWIZrDy: decomposition (if found)
The Return • Optional<string> - The
Type 🔄 decomposition or Nothing if not
possible
Let's go with Optional<string>
to return both the result and
whether it's possible!
ChEMoWIZr
Dy: C++
Implementat
ion 💻
Let's trace through
spellWithElements("BACON"):
1. Initial call: spellWithElements("BACON")
2. Try element "B":
• Rest: "ACON"
• Recursive call: spellWithElements("ACON")
Tracing 3. Try element "Ac":
• Rest: "ON"
Through 4.
• Recursive call: spellWithElements("ON")
Try element "O":
ChEMoWIZrDy • Rest: "N"
• Recursive call: spellWithElements("N")
🔍 5. Try element "N":
• Rest: "" (empty string)
• Recursive call: spellWithElements("") returns ""
6. Building back up:
• "N" + "" = "N"
• "O" + "N" = "ON"
• "Ac" + "ON" = "AcON"
• "B" + "AcON" = "BAcON"
In chemistry, element symbols have
specific capitalization:
• "He" for Helium (not "HE" or "he")
• "Na" for Sodium (not "NA" or "na")
But for our puzzle, we want to be
Handling Case case-insensitive:
Sensitivity 🔤 • "BACON" should match "B" + "Ac"
+ "O" + "N"
• "bacon" should also match "B" +
"Ac" + "O" + "N"
That's why we use toLowerCase for
comparisons!
Recursive Backtracking:
• Try each option one by one
• If an option fails, try the next one
• Don't give up until you've tried everything
Summary: Optional<T>:
Recursive • Elegant way to represent "a value or nothing"
Backtracki • Useful for functions that might not find a
solution
ng 🔑
• Cleaner than special return values or global
variables
Implementation Tips:
• Always use conditional returns inside loops
• Check for successful results before combining
• Structure code to follow the backtracking
pattern
Part 2: Big-O
Notation
Why Care
About
Efficiency?
Consider these two
functions that print all
"trigrams" (3-letter
sequences) in a string:
Which is more
efficient? How can
we measure this
formally?
Definition: Big-O notation
describes how the runtime (or
space) requirements of an
algorithm grow as the input
size increases.
Purpose:
What is Big-O • Compare algorithms
theoretically
Notation? 📈
• Predict performance for large
inputs
• Reason about scaling behavior
Key idea: We focus on the rate
of growth, not exact timing.
1. Focus on dominant terms
• If runtime is 3n² + 20n + 7, we simplify
to O(n²)
Big-O Rules: 2. Ignore constant factors
What We Care • O(3n) is the same as O(n)
• O(2n²) is the same as O(n²)
About 🔍 3. Asymptotic behavior
• What happens as n gets very large
• Small inputs are less important
Notation Name Example
Constant Array access, simple
O(1)
Common
time math
Big-O
O(log n) Logarithmic Binary search
Linear search, traversing
Complexiti O(n) Linear
an array
es 📊 O(n log n)
Linearithmi
c
Efficient sorting (merge
sort)
Listed from fastest to Nested loops, simple
O(n²) Quadratic
slowest: sorting
O(2ⁿ) Exponential Recursive enumeration
O(n!) Factorial Permutations
Analysis:
Example: • Initialization: O(1)
Analyzing • Loop: n iterations of O(1) work = O(n)
Simple Code ⌨️
• Return: O(1)
• Total: O(1) + O(n) + O(1) = O(n)
Analysis:
Example: •
•
Outer loop runs n times
For each outer loop iteration, inner loop runs n times
Nested • Total iterations: n × n = n²
Loops 🔄
• Each iteration does O(1) work
• Overall complexity: O(n²)
String •Why is substring O(k) and not O(1)?
• Creates a new string by copying characters
Operations in • More characters = more copying
C++ 🔤
Operation Complexity Reason
s.length() O(1) Length is stored
s[i] O(1) Direct access
s1 + s2 O(n) Must copy both strings
s.substr(i, k) O(k) Must copy k characters
Comparing
Loop
Structures 🧠
Analysis:
• beni: Nested loops,
O(n²)
• pando: Sequential
loops, O(n) + O(1) =
O(n)
Analyzing
Our Trigram
Example 🔍
Analysis:
• v1: O(n) loop × O(1)
work = O(n)
• v2: O(n) loop × O(n)
work (substr) = O(n²)
• v1 is more efficient!
Big-O of
Recursive
Functions 🔄
For recursive functions, we need
to consider:
• How many recursive calls are
made
• How much work is done per
call
• The depth of the recursion
Example: Finding Fibonacci
numbers recursively
This has O(2ⁿ) complexity
because each call branches
into two more calls.
Big-O of Algorithm
Complexi
ty
Reason
Our Subsets O(2ⁿ)
Binary decisions for n
Recursive elements
n choices for first, (n-1) for
Algorithms Permutations O(n!)
second, etc.
🧠 Combinations O(2ⁿ) Similar to subsets
Shrinkable n positions, potentially
O(n × n!)
Words factorial paths
Binary decisions at each
ChEMoWIZrDy O(2ⁿ)
position
Real-World Area and Volume:
• Square with side length r: Area = r² → O(r²)
Examples: • Cube with side length r: Volume = r³ → O(r³)
Geometric What happens when you double the size?
• Square: Area increases by 4× (2² = 4)
Growth 📏 • Cube: Volume increases by 8× (2³ = 8)
This is why 3D rendering is so computationally intensive!
When to optimize:
• Large inputs expected
• Algorithm runs frequently
• Performance requirements are strict
• Resource constraints exist
When to Worry When not to worry as much:
About • Small inputs only
Efficiency 🚨 • Code runs infrequently
• Readability/maintainability is more
important
• Premature optimization can be
counterproductive
Remember: "Premature optimization
is the root of all evil" - Donald Knuth
Connecti Topic Big-O Relevance
ng to
Often O(branches^depth)
Recursive Graphics
or O(n²)
Previous Recursive
Enumeration
Typically O(2ⁿ) or O(n!)
Topics 🔄 Backtracking
Can have exponential
worst case
Copying is O(n), access is
String Operations
O(1)
Decision vs. Decision can sometimes
Enumeration be more efficient
Binary
Operati Arra Linke Hash
Big-O
Search
on y d List Table
Tree
Cheat
O(log n)
Access O(1) O(n) O(1) avg
avg
Sheet 📝
O(log n)
Insert O(n) O(1) O(1) avg
avg
O(log n)
Delete O(n) O(1) O(1) avg
avg
O(log n)
Search O(n) O(n) O(1) avg
avg
1. Focus on Growth Rate:
• How does performance scale with input
size?
• Ignore constants and lower-order terms
2. Analysis Approach:
Key Takeaways: • Identify loops and their iterations
Big-O • Track operations within loops
• For recursion, count branches and depth
Notation 🔑 3. Practical Use:
• Compare algorithms theoretically
• Predict scaling behavior
• Make informed design choices
Further Learning Resources 📚
Online Resources 🌐
Interactive Learning
• VisuAlgo - Visualize algorithms and data structures
• Recursion Visualizer - Interactive tool for visualizing recursive calls
• Big-O Cheat Sheet - Quick reference for algorithm complexities
Practice Problems
• LeetCode Recursion Study Plan - Structured practice problems
• HackerRank Recursion Problems - Progressively challenging recursion exercises
• Project Euler - Mathematical problems often solved with recursion
Stanford-Specific Resources
• CS106B Course Website - All course materials and additional practice
• Stanford Algorithm Visualizations - Visualizations created for CS106B
• Past Exams and Solutions - Practice with previous years' materials