Advanced Algorithms & Problem Solving Techniques
From the Data Structures and Algorithms curriculum
TL;DR
Advanced algorithms go beyond basic sorting and searching, tackling complex problems more efficiently. Mastering techniques like dynamic programming and graph algorithms lets you solve problems that seem impossible at first glance. These skills are crucial for optimizing solutions and performing well in technical interviews.
1. The Mental Model
Think of advanced algorithms as a toolkit of specialized blueprints for solving tricky problems. Instead of building everything from scratch, you learn common, powerful patterns. Each pattern helps you break down a complex task into manageable, solvable parts.
2. The Core Material
When simple approaches fail, advanced techniques come to the rescue. They often involve smarter ways to avoid redundant work, explore possibilities, or model relationships.
Dynamic Programming (DP)

Photo by Mikhail Nilov on Pexels
DP is about solving complex problems by breaking them down into simpler subproblems. You solve each subproblem only once and store its result, so you don't recompute it later. This is great for problems with overlapping subproblems and optimal substructure (meaning an optimal solution to the problem can be constructed from optimal solutions to its subproblems).
There are two main approaches:
- Memoization (Top-down): Start with the main problem and recursively solve subproblems, storing results in a cache (e.g., a dictionary or array) as you go.
- Tabulation (Bottom-up): Start with the smallest subproblems, solve them, and use their results to build up solutions for larger subproblems until you reach the main problem.
Graph Algorithms

Photo by Google DeepMind on Pexels
Graphs are powerful ways to model relationships (e.g., social networks, road maps, dependencies). Graph algorithms help us navigate, find paths, or analyze these relationships.
Common graph problems:
- Shortest Path: Finding the shortest route between two nodes (e.g., Dijkstra's, Bellman-Ford).
- Minimum Spanning Tree (MST): Finding a subset of edges that connects all vertices with the minimum total edge weight (e.g., Prim's, Kruskal's).
- Traversal: Visiting every node (e.g., Breadth-First Search (BFS), Depth-First Search (DFS)).
BFS and DFS are fundamental. BFS explores level by level, good for shortest paths on unweighted graphs. DFS explores as deep as possible before backtracking, good for finding cycles or connected components.
graph TD
A["Problem Statement"] --> B{"Can it be broken into smaller, similar parts?"}
B -- Yes --> C{"Are there overlapping subproblems?"}
C -- Yes --> D["Consider Dynamic Programming"]
C -- No --> E["Consider Divide & Conquer"]
B -- No --> F{"Does it involve connections/relationships?"}
F -- Yes --> G["Consider Graph Algorithms"]
F -- No --> H["Explore other algorithms (Greedy, Backtracking, etc.)"]
D --> SolvedDP["Solution via DP"]
E --> SolvedDC["Solution via Divide & Conquer"]
G --> SolvedGraph["Solution via Graph Algos"]
H --> SolvedOther["Solution via Other Algos"]
Backtracking
Backtracking is a general algorithmic technique for finding all (or some) solutions to computational problems, particularly constraint satisfaction problems. It incrementally builds a solution, and if a partial solution cannot be completed to a valid solution, it "backtracks" (undoes its last step) and tries another option. Think of it like exploring a maze: you go down one path, hit a dead end, then return to the last junction to try a different path.
Greedy Algorithms

Photo by Steve A Johnson on Pexels
A greedy algorithm makes the locally optimal choice at each stage with the hope of finding a global optimum. It doesn't always work, but when it does (like in Dijkstra's for shortest path with non-negative weights or Kruskal's for MST), it's often very efficient. The key is proving that the local optimal choices indeed lead to a global optimal solution.
3. Worked Example
Let's use Dynamic Programming to solve the "Coin Change" problem:
Given a set of coin denominations coins and an amount amount, find the minimum number of coins needed to make up that amount. If it's impossible, return -1.
Example: coins = [1, 2, 5], amount = 11
Approach (Tabulation/Bottom-up DP):
- Create a DP table
dpof sizeamount + 1.dp[i]will store the minimum coins needed to make amounti. - Initialize
dp[0] = 0(0 coins to make amount 0). All otherdp[i]should beinfinity(or a very large number) to indicate they are not yet reachable. - Iterate from
i = 1toamount:- For each
i, iterate through everycoinincoins. - If
i - coin >= 0anddp[i - coin]is notinfinity, it means we can formi - coin. - Then,
dp[i] = min(dp[i], dp[i - coin] + 1). We add 1 because we are using one more coin (coin) to reachi.
- For each
- Finally,
dp[amount]will hold the answer. Ifdp[amount]is stillinfinity, it's impossible.
def coin_change(coins, amount):
# Initialize dp array with infinity, dp[0] = 0
# dp[i] will store the minimum coins needed for amount i
dp = [float('inf')] * (amount + 1)
dp[0] = 0
# Iterate through each amount from 1 up to the target amount
for i in range(1, amount + 1):
# For each amount i, try every coin denomination
for coin in coins:
# If the current coin can be used (i - coin >= 0)
# and the subproblem (i - coin) was solvable
if i - coin >= 0 and dp[i - coin] != float('inf'):
# Update dp[i] with the minimum of its current value
# and (1 + dp[i - coin]) -- 1 for the current coin,
# dp[i - coin] for the rest of the amount
dp[i] = min(dp[i], 1 + dp[i - coin])
# If dp[amount] is still infinity, it means the amount cannot be made
return dp[amount] if dp[amount] != float('inf') else -1
# Example usage:
coins_set = [1, 2, 5]
target_amount = 11
result = coin_change(coins_set, target_amount)
print(f"Minimum coins for amount {target_amount} with coins {coins_set}: {result}") # Output: 3 (5 + 5 + 1)
coins_set_2 = [2]
target_amount_2 = 3
result_2 = coin_change(coins_set_2, target_amount_2)
print(f"Minimum coins for amount {target_amount_2} with coins {coins_set_2}: {result_2}") # Output: -1 (cannot make 3 with only 2s)
The table dp for amount = 11 with coins = [1, 2, 5] would look like this during calculation:
| Amount (i) | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
dp[i] |
0 | 1 | 1 | 2 | 2 | 1 | 2 | 2 | 3 | 3 | 2 | 3 |
For amount = 11:
dp[11] is min(dp[11], 1 + dp[10]) (using coin 1) = min(inf, 1 + 2) = 3
dp[11] is min(dp[11], 1 + dp[9]) (using coin 2) = min(3, 1 + 3) = 3
dp[11] is min(dp[11], 1 + dp[6]) (using coin 5) = min(3, 1 + 2) = 3
Final dp[11] is 3.
4. Key Takeaways
- Dynamic Programming solves problems by breaking them into overlapping subproblems and storing results to avoid recomputation.
- Graph algorithms are essential for problems involving relationships or networks, like finding paths or connections.
- Backtracking is a systematic way to explore all possible solutions by building them step-by-step and undoing choices when they lead to a dead end.
- Greedy algorithms make local optimal choices, hoping to achieve a global optimum; verify their correctness for the specific problem.
- Identifying the right advanced algorithm often involves recognizing patterns like overlapping subproblems, relational structures, or exhaustive search requirements.
- These techniques significantly improve efficiency over naive brute-force solutions for complex problems.
- Practice is key to internalizing when and how to apply each technique effectively.
Common Mistakes to Avoid:
- Not recognizing a DP problem's optimal substructure or overlapping subproblems, leading to less efficient solutions.
- Misapplying a greedy approach to problems where it doesn't guarantee a globally optimal solution.
- Forgetting to handle edge cases or invalid states, especially in DP initializations or graph boundary checks.
- Getting lost in recursive calls for backtracking without proper pruning or memoization if subproblems overlap.
5. Now Try It
Choose one of the following problems and try to solve it using an advanced algorithm technique:
- Longest Common Subsequence (LCS): Given two strings
s1ands2, find the length of their longest common subsequence. (Hint: This is a classic DP problem.) - Number of Islands: Given a 2D grid map of '1's (land) and '0's (water), count the number of islands. An island is surrounded by water and is formed by connecting adjacent lands horizontally or vertically. (Hint: This is a classic graph traversal problem, typically solved with BFS or DFS.)
What success looks like: You've written a correct, reasonably efficient solution for your chosen problem. You can explain why the chosen technique (DP or graph traversal) is suitable and how it solves the problem efficiently.
Frequently asked about Advanced Algorithms & Problem Solving Techniques
Study this next
Get the full Data Structures and Algorithms curriculum
Clone the complete plan to your dashboard for unlimited AI-generated notes, practice quizzes, and a personalised revision schedule.
Create Free Account