← Back to all posts

The Grid I Didn't Need

LeetCodeTypeScriptdynamic-programmingover-engineering

The Mistake

I turned a simple greedy problem into a complex dynamic programming nightmare because I was solving for constraints that didn't exist.

The problem: Best Time to Buy and Sell Stock II

But I didn't solve this problem. I solved: "Find optimal non-overlapping profitable intervals" which is infinitely harder.

What I Built

I built this guy:

type Grid = number[][]
type Profit = [number, number, number]

function makeGrid(input: number[]): Grid {
  const grid: number[][] = []
  input.forEach((sell, daySell) => {
    input.forEach((buy, dayBuy) => {
      if (!grid[dayBuy]) grid[dayBuy] = []
      if (daySell >= dayBuy) {
        grid[dayBuy][daySell] = sell - buy  // profit matrix
      } else {
        grid[dayBuy][daySell] = 0
      }
    })
  })
  return grid
}

function solve(grid: Grid): number {
  const dailyProfits = getDailyProfits(grid)
  const positiveProfits = dailyProfits.filter(([profit]) => profit > 0)
  
  return positiveProfits.sort((a, b) => b[0] - a[0]).reduce((acc, val) => {
    const [profit, rowId, colId] = val 
    const subGrid = getSubgrid(colId+1, grid)  // recursive madness
    const free: number = solve(subGrid)
    return [...acc, profit + free]
  }, [] as number[])[0]
}

A 2D grid where grid[buyDay][sellDay] = profit. Then recursive optimization to find non-overlapping profitable intervals.

Why? Because I saw the problem wrong.

How I Saw The Problem

I visualized it as finding optimal contiguous subarrays:

// Price: [1     5]   [2     6]  3  
//      buy↑ sell↑  buy↑ sell↑    
//        profit=4    profit=4

This led me to think: "I need to partition the array into non-overlapping profitable segments and optimize the partition."

Classic interval scheduling problem → dynamic programming → recursive optimization → 2D grids.

The Insight

But here's what I missed: Any profitable interval can be decomposed into individual day-to-day gains without losing profit.

Take the interval [1, 3, 5]:

Since you can "buy and sell on the same day", you can chain transactions instantly. This means:

Net result: captured $(3-1) + $(5-3) = +4$

The mathematical equivalence: Every profitable "mountain" decomposes into profitable steps.

The Fix

All I actually needed:

function maxProfit(prices: number[]): number {
    let profit = 0;
    
    for (let i = 1; i < prices.length; i++) {
        if (prices[i] > prices[i - 1]) {
            profit += prices[i] - prices[i - 1];
        }
    }
    
    return profit;
}

No grid. No recursion. No interval optimization. Just add up every profitable day-to-day move.

For [7,1,5,3,6,4]:

Lesson Learned

Before building complex solutions, ask: "What constraints am I actually solving for?"

I was solving for constraints that didn't exist:

The problem statement contained the key: "can buy and sell on the same day." This phrase eliminates the entire complexity of interval scheduling.

Sometimes the sophisticated solution shows you can handle complexity, but the real skill is recognizing when complexity isn't needed.

My 2D grid approach would be brilliant for "Best Time to Buy and Sell Stock III" (at most K transactions). But for the unlimited transactions variant, it's like bringing a Formula 1 car to a go-kart race.

My overengineered code solution

Original problem: LeetCode 122: Best Time to Buy and Sell Stock II