Recursion is a method where a function calls itself to solve smaller parts of a problem until it reaches a simple base case.
Click to reveal answer
intermediate
What does DP (Dynamic Programming) do differently from plain recursion?
DP stores results of solved subproblems to avoid repeating work, making it faster than plain recursion for problems with overlapping subproblems.
Click to reveal answer
beginner
When is a greedy algorithm the right choice?
A greedy algorithm works well when choosing the best option at each step leads to an overall optimal solution, without needing to reconsider previous choices.
Click to reveal answer
intermediate
What kind of problems are best solved by DP instead of greedy?
Problems with overlapping subproblems and where local best choices don’t guarantee a global best solution are best solved by DP.
Click to reveal answer
advanced
How can you decide between recursion, DP, and greedy for a new problem?
Check if the problem has overlapping subproblems and optimal substructure. Use recursion for simple cases, DP if overlapping subproblems exist, and greedy if local choices lead to global optimum.
Click to reveal answer
Which approach stores results to avoid repeated calculations?
ADynamic Programming
BGreedy Algorithm
CSimple Recursion
DBrute Force
✗ Incorrect
Dynamic Programming saves results of subproblems to avoid recalculating them.
When is greedy algorithm NOT a good choice?
AWhen recursion is too slow
BWhen problem has no overlapping subproblems
CWhen local choices don’t guarantee global optimum
DWhen problem is very simple
✗ Incorrect
Greedy fails if choosing the best local option doesn’t lead to the best overall solution.
What is a base case in recursion?
AA loop inside recursion
BThe largest input size
CA repeated calculation
DThe simplest case that stops recursion
✗ Incorrect
Base case stops the recursion by providing a direct answer.
Which technique is best for problems with overlapping subproblems?
AGreedy Algorithm
BDynamic Programming
CSimple Recursion
DSorting
✗ Incorrect
DP efficiently solves problems with overlapping subproblems by caching results.
What does 'optimal substructure' mean?
AOptimal solution can be built from optimal solutions of subproblems
BProblem has no solution
CSolution requires brute force
DProblem is unsolvable
✗ Incorrect
Optimal substructure means solving subproblems optimally helps solve the whole problem optimally.
Explain how to choose between recursion, dynamic programming, and greedy algorithms for solving a problem.
Think about problem structure and if repeated work happens.
You got /5 concepts.
Describe a real-life example where a greedy algorithm works well and one where dynamic programming is better.
Consider simple step-by-step choices vs. complex overlapping decisions.
You got /3 concepts.
Practice
(1/5)
1. Which approach is best suited when a problem has overlapping subproblems and optimal substructure?
easy
A. Dynamic Programming
B. Greedy Algorithm
C. Simple Recursion without memoization
D. Brute Force
Solution
Step 1: Understand problem characteristics
Overlapping subproblems means the same smaller problems repeat. Optimal substructure means the best solution can be built from best solutions of smaller problems.
Step 2: Match approach to characteristics
Dynamic Programming stores results of subproblems to avoid recomputation and uses optimal substructure to build the solution efficiently.
Hint: Use DP when subproblems repeat and build optimal solutions [OK]
Common Mistakes:
Choosing greedy when subproblems overlap
Using simple recursion without memoization causing slow code
Confusing optimal substructure with greedy choice property
2. Which of the following Rust function signatures correctly represents a recursive function that returns an integer?
easy
A. fn factorial(n: u32) -> u32 { let mut result = 1; for i in 1..=n { result *= i; } result }
B. fn factorial(n: u32) { if n == 0 { 1 } else { n * factorial(n - 1) } }
C. fn factorial(n: u32) -> String { if n == 0 { "1".to_string() } else { (n * factorial(n - 1)).to_string() } }
D. fn factorial(n: u32) -> u32 { if n == 0 { 1 } else { n * factorial(n - 1) } }
Solution
Step 1: Check function return type and recursion
fn factorial(n: u32) -> u32 { if n == 0 { 1 } else { n * factorial(n - 1) } } returns u32 and calls itself recursively correctly. fn factorial(n: u32) { if n == 0 { 1 } else { n * factorial(n - 1) } } lacks return type, so it returns unit ().
Step 2: Validate correctness of recursion and return
fn factorial(n: u32) -> u32 { if n == 0 { 1 } else { n * factorial(n - 1) } } correctly returns the factorial value recursively. fn factorial(n: u32) -> u32 { let mut result = 1; for i in 1..=n { result *= i; } result } is iterative, not recursive. fn factorial(n: u32) -> String { if n == 0 { "1".to_string() } else { (n * factorial(n - 1)).to_string() } } returns String, not integer.
Final Answer:
fn factorial(n: u32) -> u32 { if n == 0 { 1 } else { n * factorial(n - 1) } } -> Option D
Quick Check:
Recursive function with correct return type = fn factorial(n: u32) -> u32 { if n == 0 { 1 } else { n * factorial(n - 1) } } [OK]
Hint: Recursive functions must specify return type matching recursive calls [OK]
Common Mistakes:
Omitting return type causes compilation errors
Confusing iterative and recursive implementations
Returning wrong data types in recursion
3. What is the output of the following Rust code using recursion to compute Fibonacci numbers?
fn fib(n: u32) -> u32 {
if n <= 1 {
n
} else {
fib(n - 1) + fib(n - 2)
}
}
fn main() {
println!("{}", fib(5));
}
medium
A. 13
B. 5
C. 3
D. 8
Solution
Step 1: Understand Fibonacci recursion
fib(5) = fib(4) + fib(3). Calculate fib(4) and fib(3) similarly until base cases fib(1)=1 and fib(0)=0.
Hint: Trace base cases and add results up to n [OK]
Common Mistakes:
Confusing fib(5) with fib(6)
Miscounting base cases
Assuming fib(0) = 1 instead of 0
4. The following Rust code attempts to implement a greedy algorithm for coin change but has a bug. What is the bug?
fn coin_change(coins: &[u32], amount: u32) -> u32 {
let mut remaining = amount;
let mut count = 0;
for &coin in coins {
count += remaining / coin;
remaining %= coin;
}
if remaining == 0 { count } else { 0 }
}
fn main() {
let coins = [1, 2, 5];
println!("{}", coin_change(&coins, 11));
}
medium
A. The loop should break when remaining is zero
B. The function returns 0 instead of -1 when change is impossible
C. Coins are not sorted in descending order, so greedy fails
D. The function uses integer division which causes errors
Solution
Step 1: Check coin order
Greedy coin change requires coins sorted in descending order to pick largest first. Here coins=[1, 2, 5] are ascending, so it picks smallest first.
Step 2: Trace execution
For amount=11: coin=1, count=11, remaining=0; subsequent coins add 0. Returns 11, but optimal is 3 (5+5+1). Greedy suboptimal.
Final Answer:
Coins are not sorted in descending order, so greedy fails -> Option C
Quick Check:
Greedy needs descending coin order [OK]
Hint: Always sort coins descending for greedy coin change [OK]
Common Mistakes:
Assuming greedy always works for any coin set
Ignoring coin order importance
Confusing return values for impossible change
5. You want to find the minimum number of coins needed to make a certain amount using coins of denominations [1, 3, 4]. Which approach is best and why?
hard
A. Use dynamic programming to store minimum coins needed for smaller amounts and build up
B. Use recursion without memoization to explore all combinations
C. Use a greedy algorithm because picking the largest coin first always gives minimum coins
D. Use simple iteration over coins without storing intermediate results
Solution
Step 1: Analyze greedy suitability
Greedy fails here because picking largest coin first (4) may not lead to minimum coins for all amounts (e.g., amount=6).
Step 2: Consider recursion and DP
Recursion without memoization repeats calculations and is inefficient. Dynamic programming stores results for smaller amounts, avoiding recomputation and ensuring minimum coins.
Final Answer:
Use dynamic programming to store minimum coins needed for smaller amounts and build up -> Option A
Quick Check:
Minimum coins with overlapping subproblems = Dynamic Programming [OK]
Hint: DP stores sub-results to find minimum coins efficiently [OK]
Common Mistakes:
Assuming greedy always works for coin change
Using recursion without memoization causing slow code
Ignoring overlapping subproblems in coin change
Recommended
Ready to prove it?
Pass the test and this pattern moves unknownstrongwhile earning +1 Grip toward your certificate.