Problem
Given a positive integer numRows, return the first numRows rows of Pascal's triangle — a number triangle where every edge cell is 1 and every interior cell is the sum of the two cells directly above it.
- Input:
numRows = 5 - Output:
[[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]] - Explanation: each new row is built by placing 1s at both ends and summing adjacent pairs from the row above.
Counter-example: [1, 3, 3] is not a valid row because it is missing the trailing 1.
Intuition
Every row can be derived entirely from the previous row: the first and last elements are always 1, and each interior element at position j equals prev[j-1] + prev[j]. This means we never need to look back more than one row, and we can build the entire triangle top-down in a single pass.
Solution — Iterative Row Construction
Start with [[1]] and repeatedly derive the next row from the last one — prepend and append 1, then fill the interior by summing adjacent pairs from the previous row.
- Initialize
trianglewith the first row[1]. - Loop from row index 1 up to
numRows - 1. - For each new row, start with
[1]. - For each interior position
jfrom 1 tolen(prev_row) - 1(inclusive), appendprev_row[j-1] + prev_row[j]. - Append the trailing
1and add the completed row totriangle.
1def generate(numRows: int) -> list[list[int]]:
2 triangle = [[1]]
3 for i in range(1, numRows):
4 prev_row = triangle[i - 1]
5 current_row = [1]
6 for j in range(1, len(prev_row)): # interior elements only
7 current_row.append(prev_row[j - 1] + prev_row[j])
8 current_row.append(1) # every row ends with 1
9 triangle.append(current_row)
10 return triangleTime: O(numRows²) — we produce approximately numRows²/2 total elements across all rows.
Space: O(numRows²) — the output itself requires storing every element; no extra space beyond the result.
Complexity Summary
| Approach | Time | Space | When to use |
|---|---|---|---|
| Iterative row construction | O(numRows²) | O(numRows²) | Any time the full triangle is needed; there is no asymptotically better alternative. |
Common Mistakes
- Using
range(1, len(prev_row) - 1)for the inner loop — this silently skips the last interior element. Forprev_row = [1, 2, 1], it computesj=1only and produces[1, 3, 1]instead of[1, 3, 3, 1]. The correct bound isrange(1, len(prev_row)). - Accessing
prev_row[j] + prev_row[j+1]instead ofprev_row[j-1] + prev_row[j]— whenjreacheslen(prev_row) - 1,prev_row[j+1]is an out-of-bounds access. Shifting the index toj-1andjlets the samerange(1, len(prev_row))bounds work safely. - Forgetting the trailing 1 — both the leading and trailing 1 must be added explicitly; the inner loop only fills interior positions.
- Starting with an empty
trianglelist — iftrianglestarts empty, the loop has no previous row to reference fori=0. Seeding with[[1]]and starting the loop ati=1avoids this. - Confusing
numRowswith row index — ifnumRows = 5, the output should contain rows at indices 0, 1, 2, 3, 4; the loop rangerange(1, numRows)produces 4 iterations starting from the already-seeded row 0, which is correct.
Related Problems
pascals-triangle-ii— return only thekth row in O(k) space instead of the full triangletriangle— find the minimum-cost path from top to bottom of a triangular grid using the same row-by-row DP structureunique-paths— the number of grid paths equals a value from Pascal's triangle (C(m+n-2, m-1)), connecting combinatorics to DPclimbing-stairs— another bottom-up DP where each value is derived from the previous one or two valuescoin-change— builds a 1D DP table bottom-up in a similar left-to-right construction pattern