Pascal's Triangle
Given an integer numRows, return the first numRows of Pascal's triangle. In Pascal's triangle, each number is the sum of the two numbers directly above it.
- 1 <= numRows <= 30
numRows = 5[[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]numRows = 1[[1]]numRows = 3[[1],[1,1],[1,2,1]]Pascal's triangle is a stack of rows of numbers. Row 0 is just [1]. Every later row is one entry longer than the previous, starts and ends with 1, and every interior entry equals the sum of the two entries sitting diagonally above it — above-left plus above-right. Row 3 is [1, 3, 3, 1] because each 3 is the sum of the 1 and 2 above it. The task: return the first numRows rows as a list of lists. This is our first two-dimensional output: a list where each element is itself a list (one per row).
The definition looks recursive, so the naive move is a function that computes any cell by asking for the two cells above it:
def get_val(r, c):
if c == 0 or c == r: return 1 # edges are always 1
return get_val(r-1, c-1) + get_val(r-1, c)Trace what actually happens: computing one cell in row 10 spawns two calls into row 9, four into row 8, eight into row 7... the call count roughly doubles per level, and — this is the real crime — the same cells get recomputed over and over, because two neighbors in row r both ask for the cell they share in row r-1. Exponential work to rebuild values we already produced moments earlier.
Flip the direction. Instead of starting at the target and asking upward, build downward from row 0 and keep every row you finish. When row i is being built, its entire parent row i-1 already sits in memory — every interior entry is one addition, no recursion, no recomputation. This is the same instinct that powered Kadane's algorithm: store the answer to the previous subproblem and build the next answer from it directly. Here the subproblem is simply "the previous row".
triangle = [[1]]
for i in range(1, num_rows):
prev = triangle[i-1] # the parent row, fully computed
curr = [1] # every row starts with 1
for j in range(1, i): # row i has i+1 entries; interior is j = 1..i-1
curr.append(prev[j-1] + prev[j])
curr.append(1) # and ends with 1
triangle.append(curr)
return triangleTotal entries produced: 1 + 2 + ... + numRows, about numRows²/2. Each costs one addition, so O(numRows²) time and space. Could a cleverer algorithm be faster? No — and the reason is worth internalizing: the output itself contains O(numRows²) numbers, and no algorithm can run faster than the time needed to write its own answer down. When output size matches your runtime, you are done optimizing. That "compare against the size of the answer" check is a habit that instantly tells you whether further optimization is even possible.
Triangle Construction
RECURRENCE RELATION
Each cell (i, j) = Cell (i-1, j-1) + Cell (i-1, j). This symmetry builds the entire structure from the edges inward.
EFFICIENCY
We process O(N²) cells in total. By building layer by layer, we avoid redundant calculations—the core of Dynamic Programming.