Why Backtracking and What Greedy Cannot Solve in DSA Typescript - Performance Analysis
We want to understand why some problems need backtracking instead of greedy methods.
How does the time needed grow when we try all possibilities versus making quick choices?
Analyze the time complexity of this backtracking example for subset sum.
function subsetSum(nums: number[], target: number, index = 0): boolean {
if (target === 0) return true;
if (index === nums.length || target < 0) return false;
// Choose current number
if (subsetSum(nums, target - nums[index], index + 1)) return true;
// Skip current number
return subsetSum(nums, target, index + 1);
}
This code tries all combinations of numbers to find if any sum to the target.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Recursive calls exploring two choices at each step.
- How many times: Up to 2 calls per element, leading to many combinations.
Each number doubles the possible combinations to check.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 1,024 calls |
| 20 | About 1,048,576 calls |
| 30 | About 1,073,741,824 calls |
Pattern observation: Operations grow exponentially as input size increases.
Time Complexity: O(2^n)
This means the time doubles with each extra element, making it slow for large inputs.
[X] Wrong: "Greedy choices always find the best solution quickly."
[OK] Correct: Greedy methods pick locally best steps but can miss the overall best answer, especially when choices depend on future options.
Understanding when to use backtracking versus greedy shows your problem-solving depth and helps you choose the right tool for complex problems.
What if we added memoization to this backtracking? How would the time complexity change?
Practice
Solution
Step 1: Understand backtracking's approach
Backtracking tries all possible options and can go back (undo) if a choice leads to failure.Step 2: Compare with greedy approach
Greedy algorithms make the best immediate choice but cannot undo it, so they may miss the correct solution.Final Answer:
Because backtracking explores all possible solutions and can undo choices, while greedy makes irreversible choices -> Option BQuick Check:
Backtracking = explores all + undo [OK]
- Thinking greedy always finds the best solution
- Confusing backtracking with brute force
- Believing backtracking uses less memory
Solution
Step 1: Check TypeScript function syntax
Correct syntax uses colon ':' to specify types and parentheses for parameters.Step 2: Verify return type and arrow usage
function backtrack(path: number[], options: number[]): void uses correct syntax with void return type; others have syntax errors or wrong return type.Final Answer:
function backtrack(path: number[], options: number[]): void -> Option CQuick Check:
Correct TypeScript function syntax = function backtrack(path: number[], options: number[]): void [OK]
- Missing colons between parameter and type
- Using arrow '=>' incorrectly in function signature
- Wrong return type specified
function backtrack(start: number, path: number[], nums: number[], result: number[][]) {
result.push([...path]);
for (let i = start; i < nums.length; i++) {
path.push(nums[i]);
backtrack(i + 1, path, nums, result);
path.pop();
}
}
const nums = [1, 2];
const result: number[][] = [];
backtrack(0, [], nums, result);
console.log(result);
What is the output printed?Solution
Step 1: Trace backtracking calls with nums = [1, 2]
Start with empty path [], add it to result. Then add 1, recurse, add [1], add 2, recurse, add [1,2], backtrack, add 2 alone, add [2].Step 2: Collect all subsets generated
Subsets are [], [1], [1, 2], and [2] in order.Final Answer:
[[], [1], [1, 2], [2]] -> Option DQuick Check:
All subsets generated = [[], [1], [1, 2], [2]] [OK]
- Forgetting to add empty subset
- Not popping after recursion causing wrong subsets
- Mixing order of subsets
function permute(nums: number[], path: number[], used: boolean[], result: number[][]) {
if (path.length === nums.length) {
result.push(path);
return;
}
for (let i = 0; i < nums.length; i++) {
if (used[i]) continue;
path.push(nums[i]);
used[i] = true;
permute(nums, path, used, result);
path.pop();
used[i] = false;
}
}
const result: number[][] = [];
permute([1, 2], [], [false, false], result);
console.log(result);
What is the bug and how to fix it?Solution
Step 1: Identify the problem with pushing path
Pushing path directly adds the same array reference multiple times, causing all entries to be the same final path.Step 2: Fix by pushing a copy of path
Use result.push([...path]) to push a new array copy each time.Final Answer:
Bug: Pushing path directly causes reference issues; fix by pushing a copy of path -> Option AQuick Check:
Push copy of path, not reference [OK]
- Pushing path reference instead of copy
- Incorrect base case condition
- Misusing used array updates
Solution
Step 1: Understand greedy approach limitation
Greedy picks the largest numbers first, which may overshoot or miss the exact target sum.Step 2: How backtracking solves the problem
Backtracking tries all subsets by including or excluding each number, ensuring it finds an exact sum if it exists.Final Answer:
Greedy fails because picking largest numbers first may miss exact sum; backtracking tries all subsets to find exact match -> Option AQuick Check:
Subset sum needs all subsets tried = Greedy fails because picking largest numbers first may miss exact sum; backtracking tries all subsets to find exact match [OK]
- Thinking greedy always finds exact sum
- Confusing sorting with solving approach
- Believing backtracking is iterative only
Ready to prove it?
Pass the test and this pattern moves unknown
