0
0
DSA Typescriptprogramming

Why Backtracking and What Greedy Cannot Solve in DSA Typescript - Why This Pattern

Choose your learning style9 modes available
Mental Model
Backtracking tries all possible choices step-by-step to find the right answer, while greedy picks the best choice now but might miss the best overall.
Analogy: Imagine choosing a path in a maze: greedy picks the closest exit at each turn but might get stuck; backtracking tries every path until it finds the exit.
Start
  ↓
Choice 1 -> Dead end
  ↓
Choice 2 -> Possible path -> Goal
Dry Run Walkthrough
Input: Find subset of numbers [3, 1, 7, 9] that sums to 11
Goal: Show greedy picks wrong subsets, backtracking finds correct subset
Step 1: Greedy picks largest number 9 first
[9] sum=9
Why: Greedy chooses biggest number to get close to 11 quickly
Step 2: Greedy tries to add next largest number 7 but sum exceeds 11
[9] sum=9 (cannot add 7)
Why: Adding 7 makes sum 16 which is too big
Step 3: Greedy tries next number 3 but sum exceeds 11 again
[9] sum=9 (cannot add 3)
Why: Adding 3 makes sum 12 which is too big
Step 4: Greedy tries next number 1 and sum becomes 10
[9,1] sum=10
Why: Adding 1 reaches sum 10 but target is 11
Step 5: Backtracking tries subsets starting from empty set
[] sum=0
Why: Backtracking explores all options
Step 6: Backtracking adds 3, sum=3
[3] sum=3
Why: Try including 3
Step 7: Backtracking adds 1 then 7, sum=11 found
[3,1,7] sum=11
Why: Found subset that sums exactly to 11
Result:
[3,1,7] sum=11 is correct subset found by backtracking; greedy found [9,1] sum=10 but failed to reach target
Annotated Code
DSA Typescript
class SubsetSum {
  nums: number[];
  target: number;

  constructor(nums: number[], target: number) {
    this.nums = nums;
    this.target = target;
  }

  greedySolution(): number[] {
    let sum = 0;
    const result: number[] = [];
    // Sort descending for greedy pick
    const sorted = [...this.nums].sort((a, b) => b - a);
    for (const num of sorted) {
      if (sum + num <= this.target) {
        result.push(num); // add number if sum not exceeded
        sum += num;
      }
      if (sum === this.target) {
        break; // stop if target reached
      }
    }
    return result;
  }

  backtrackingSolution(): number[] {
    const result: number[] = [];
    const path: number[] = [];

    const backtrack = (index: number, currentSum: number): boolean => {
      if (currentSum === this.target) {
        result.push(...path); // found valid subset
        return true;
      }
      if (currentSum > this.target || index === this.nums.length) {
        return false; // stop if sum exceeded or no more numbers
      }

      // choose current number
      path.push(this.nums[index]);
      if (backtrack(index + 1, currentSum + this.nums[index])) {
        return true; // found solution
      }
      path.pop(); // backtrack

      // skip current number
      if (backtrack(index + 1, currentSum)) {
        return true; // found solution skipping current
      }

      return false; // no solution found here
    };

    backtrack(0, 0);
    return result;
  }
}

// Driver code
const nums = [3, 1, 7, 9];
const target = 11;
const subsetSum = new SubsetSum(nums, target);

console.log('Greedy solution:', subsetSum.greedySolution().join(', '));
console.log('Backtracking solution:', subsetSum.backtrackingSolution().join(', '));
const sorted = [...this.nums].sort((a, b) => b - a);
sort numbers descending for greedy choice
if (sum + num <= this.target) { result.push(num); sum += num; }
add number if sum does not exceed target
if (currentSum === this.target) { result.push(...path); return true; }
found subset that sums exactly to target
if (currentSum > this.target || index === this.nums.length) { return false; }
stop exploring if sum exceeded or no more numbers
path.push(this.nums[index]); if (backtrack(index + 1, currentSum + this.nums[index])) { return true; } path.pop();
try including current number and recurse
if (backtrack(index + 1, currentSum)) { return true; }
try skipping current number and recurse
OutputSuccess
Greedy solution: 9, 1 Backtracking solution: 3, 1, 7
Complexity Analysis
Time: O(2^n) because backtracking tries all subsets, greedy is O(n log n) due to sorting
Space: O(n) for recursion stack and path storage in backtracking, O(n) for sorting in greedy
vs Alternative: Greedy is faster but can miss solutions; backtracking is slower but finds all valid subsets
Edge Cases
empty input array
backtracking returns empty subset, greedy returns empty subset
DSA Typescript
if (currentSum > this.target || index === this.nums.length) { return false; }
no subset sums to target
backtracking returns empty array, greedy returns partial sum subset
DSA Typescript
if (currentSum === this.target) { return true; }
target is zero
backtracking returns empty subset immediately, greedy returns empty subset
DSA Typescript
if (currentSum === this.target) { return true; }
When to Use This Pattern
When a problem asks for all or some combinations that satisfy a condition and greedy fails, reach for backtracking to explore all possibilities.
Common Mistakes
Mistake: Using greedy approach expecting it to always find correct subset sum
Fix: Use backtracking to explore all subsets when greedy fails
Mistake: Not backtracking (undoing) after trying a choice
Fix: Always remove last choice before trying next option to explore all paths
Summary
Backtracking tries all possible choices to find correct solutions where greedy fails.
Use backtracking when greedy choices do not guarantee correct or complete answers.
The key is exploring all options step-by-step and undoing choices to find the right path.