Skip to Content
Course content

186: Dynamic Programming Patterns in PHP

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I've seen this happen more times than I can count: a developer writes a recursive function that looks elegant and mathematically "correct" on a whiteboard, but the second it hits production with real-world data, the server CPU spikes to 100% and the request times out. This usually happens because the code is solving the same sub-problem millions of times.

Take a look at this attempt to solve a "Staircase" problem—calculating how many ways you can reach the top of a staircase with $n$ steps, given you can climb either 1 or 2 steps at a time.

<?php
function countWays(int $n): int {
    if ($n <= 1) {
        return 1;
    }
    // The number of ways to reach step n is the sum of ways 
    // to reach (n-1) and (n-2)
    return countWays($n - 1) + countWays($n - 2);
}

echo countWays(10); // Works instantly: 89
echo countWays(40); // Hangs... takes forever...
?>

The Exponential Wall of Naive Recursion

If you run the code above with 10, it's fine. But try 40 or 50, and your PHP process will likely hit the max_execution_time limit. Why? Because this is a "naive" recursive approach. To calculate countWays(40), the engine has to calculate countWays(39) and countWays(38). But to calculate countWays(39), it also has to calculate countWays(38) and countWays(37).

We are calculating the value for step 38 twice. Then we calculate 37 three times. By the time we get down to the base cases, we've branched into an exponential number of redundant function calls. This is $O(2^n)$ time complexity, which is a death sentence for any application with a growing input.

Adding a Memory Cache to Your Recursion

This is where Dynamic Programming (DP) comes in. The core idea is simple: don't calculate the same thing twice. The most intuitive way to implement this in PHP is through "Memoization." We essentially create a cache (usually an array) to store the result of each sub-problem the first time we solve it.

<?php
class StaircaseSolver {
    private array $memo = [];

    public function countWays(int $n): int {
        // 1. Check if we've already solved this specific n
        if (isset($this->memo[$n])) {
            return $this->memo[$n];
        }

        // 2. Base cases
        if ($n <= 1) {
            return 1;
        }

        // 3. Calculate, store in memo, then return
        $this->memo[$n] = $this->countWays($n - 1) + $this->countWays($n - 2);
        return $this->memo[$n];
    }
}

$solver = new StaircaseSolver();
echo $solver->countWays(40); // Instantaneous: 165580141
?>

By adding that $memo array, we've transformed the complexity from exponential $O(2^n)$ to linear $O(n)$. We only calculate the value for each step exactly once.

Trading Space for Speed with Tabulation

While memoization (top-down) is great, it still uses the call stack. If $n$ is massive (like 10,000), you'll hit the recursion limit or a stack overflow. The alternative DP pattern is "Tabulation" (bottom-up). Instead of starting at $n$ and working backward, we start at the base case and build a table forward.

I generally prefer tabulation when I know exactly what the range of sub-problems is, as it's often slightly faster and avoids stack issues entirely.

<?php
function countWaysTabulated(int $n): int {
    if ($n <= 1) return 1;

    // Create a table to store results from 0 up to n
    $table = [];
    $table[0] = 1; 
    $table[1] = 1;

    for ($i = 2; $i <= $n; $i++) {
        $table[$i] = $table[$i - 1] + $table[$i - 2];
    }

    return $table[$n];
}
?>

Notice that we aren't calling functions anymore; we're just filling an array. If you want to be a real memory wizard, you'll notice we only ever need the last two values to calculate the next one. You could actually replace the entire array with just two variables, reducing your space complexity from $O(n)$ to $O(1)$.




📋 Practical Task

Build a Minimum-Cost Path Grid Solver

You are tasked with finding the minimum cost to travel from the top-left corner (0,0) to the bottom-right corner (m,n) of a grid. You can only move down or right. Each cell in the grid contains a cost (an integer).

The Challenge:
Implement a class GridPathSolver with a method findMinCost(array $grid). You must use a Dynamic Programming pattern (either Memoization or Tabulation) to ensure that the solver can handle a 50x50 grid without timing out.

Requirements:

  • The input is a 2D array of integers.
  • The output should be a single integer representing the minimum total cost.
  • Your solution must avoid naive recursion to prevent exponential time complexity.

Example Input:

$grid = [
    [1, 3, 1],
    [1, 5, 1],
    [4, 2, 1]
];
// Expected Output: 7 (Path: 1 → 3 → 1 → 1 → 1)
Rating
0 0

There are no comments for now.

to be the first to leave a comment.