118. Pascal's Triangle
Problem Description
You need to generate Pascal's triangle with a given number of rows.
Pascal's triangle is a triangular array of numbers where:
- The first row contains just the number
1
- Each subsequent row starts and ends with
1
- Every other number in a row is the sum of the two numbers directly above it from the previous row
For example, if numRows = 5
, the output would be:
1 1 1 1 2 1 1 3 3 1 1 4 6 4 1
Which is represented as a list of lists: [[1], [1,1], [1,2,1], [1,3,3,1], [1,4,6,4,1]]
The pattern works by adding adjacent elements from the previous row:
- Row 3:
[1, 2, 1]
where2 = 1 + 1
(sum of the two elements above it) - Row 4:
[1, 3, 3, 1]
where3 = 1 + 2
and3 = 2 + 1
- Row 5:
[1, 4, 6, 4, 1]
where4 = 1 + 3
,6 = 3 + 3
, and4 = 3 + 1
Given an integer numRows
, return the first numRows
of Pascal's triangle as a list of lists, where each inner list represents a row of the triangle.
Intuition
The key observation is that each row of Pascal's triangle can be built from the previous row. Since each number (except the edges) is the sum of the two numbers directly above it, we can generate each new row by taking adjacent pairs from the previous row and adding them together.
Think about how we construct row by row:
- Start with the first row
[1]
- For the next row, we need
[1, 1]
- the edges are always1
- For the third row
[1, 2, 1]
, the middle element2
comes from adding the adjacent pair(1, 1)
from the previous row - For the fourth row
[1, 3, 3, 1]
, we get the middle elements by adding pairs:(1, 2) → 3
and(2, 1) → 3
This suggests a pattern: for each new row, we can:
- Start with
1
(the left edge) - Add all sums of adjacent pairs from the previous row
- End with
1
(the right edge)
The solution uses pairwise(f[-1])
to iterate through consecutive pairs of elements in the last row. For a row like [1, 2, 1]
, pairwise
gives us (1, 2)
and (2, 1)
. We sum each pair to get [3, 3]
, then wrap this with 1
s on both sides to get [1, 3, 3, 1]
.
This approach naturally handles all cases - even when the previous row has only one element [1]
, pairwise
returns no pairs, so we just get [1] + [] + [1] = [1, 1]
, which is exactly what we want for the second row.
Solution Approach
We use a simulation approach to build Pascal's triangle row by row.
Step 1: Initialize the triangle
- Create a list
f
with the first row[[1]]
- This serves as our base case since the first row is always
[1]
Step 2: Generate remaining rows
- Loop
numRows - 1
times to generate the remaining rows (since we already have the first row) - For each iteration
i
, we build a new rowg
based on the last row in our trianglef[-1]
Step 3: Construct each new row
For each new row g
, we use the formula:
g = [1] + [a + b for a, b in pairwise(f[-1])] + [1]
Breaking this down:
[1]
- Start with 1 (left edge)[a + b for a, b in pairwise(f[-1])]
- Calculate middle elements by summing adjacent pairs from the previous row[1]
- End with 1 (right edge)
The pairwise
function iterates through consecutive elements in the last row. For example:
- If
f[-1] = [1, 2, 1]
, thenpairwise(f[-1])
yields(1, 2)
and(2, 1)
- Summing these pairs gives us
[3, 3]
- Adding the edge 1s gives us the complete row:
[1, 3, 3, 1]
Step 4: Append and return
- After constructing each row
g
, append it to our trianglef
- After all rows are generated, return the complete triangle
f
The time complexity is O(numRows²)
since we generate numRows
rows, and each row can have up to numRows
elements. The space complexity is also O(numRows²)
for storing the entire triangle.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through generating Pascal's triangle with numRows = 4
.
Initial State:
- Start with
f = [[1]]
(the first row)
Iteration 1: Generate Row 2
- Previous row:
f[-1] = [1]
- Apply
pairwise([1])
: This yields no pairs (empty sequence) - Calculate middle elements:
[]
(no pairs to sum) - Construct new row:
g = [1] + [] + [1] = [1, 1]
- Update triangle:
f = [[1], [1, 1]]
Iteration 2: Generate Row 3
- Previous row:
f[-1] = [1, 1]
- Apply
pairwise([1, 1])
: This yields one pair(1, 1)
- Calculate middle elements:
[1 + 1] = [2]
- Construct new row:
g = [1] + [2] + [1] = [1, 2, 1]
- Update triangle:
f = [[1], [1, 1], [1, 2, 1]]
Iteration 3: Generate Row 4
- Previous row:
f[-1] = [1, 2, 1]
- Apply
pairwise([1, 2, 1])
: This yields pairs(1, 2)
and(2, 1)
- Calculate middle elements:
[1 + 2, 2 + 1] = [3, 3]
- Construct new row:
g = [1] + [3, 3] + [1] = [1, 3, 3, 1]
- Update triangle:
f = [[1], [1, 1], [1, 2, 1], [1, 3, 3, 1]]
Final Result:
Return [[1], [1, 1], [1, 2, 1], [1, 3, 3, 1]]
This represents Pascal's triangle:
1 1 1 1 2 1 1 3 3 1
Each row is constructed by taking the previous row, summing adjacent pairs to get the middle elements, and wrapping the result with 1s on both edges.
Solution Implementation
1class Solution:
2 def generate(self, numRows: int) -> List[List[int]]:
3 # Initialize Pascal's triangle with the first row [1]
4 triangle = [[1]]
5
6 # Generate each subsequent row
7 for row_index in range(numRows - 1):
8 # Create new row starting with 1
9 # Add sums of adjacent pairs from previous row
10 # End with 1
11 current_row = [1]
12
13 # Get the last row from triangle
14 previous_row = triangle[-1]
15
16 # Calculate middle elements by summing adjacent pairs
17 for j in range(len(previous_row) - 1):
18 current_row.append(previous_row[j] + previous_row[j + 1])
19
20 # Add trailing 1
21 current_row.append(1)
22
23 # Add the completed row to triangle
24 triangle.append(current_row)
25
26 return triangle
27
1class Solution {
2 /**
3 * Generates Pascal's Triangle with the specified number of rows.
4 * Each row contains coefficients of binomial expansion.
5 *
6 * @param numRows The number of rows to generate
7 * @return A list of lists representing Pascal's Triangle
8 */
9 public List<List<Integer>> generate(int numRows) {
10 // Initialize the result list to store all rows of Pascal's Triangle
11 List<List<Integer>> pascalTriangle = new ArrayList<>();
12
13 // Add the first row which always contains only 1
14 pascalTriangle.add(List.of(1));
15
16 // Generate remaining rows (from row 2 to numRows)
17 for (int rowIndex = 0; rowIndex < numRows - 1; rowIndex++) {
18 // Create a new row for the current iteration
19 List<Integer> currentRow = new ArrayList<>();
20
21 // First element of each row is always 1
22 currentRow.add(1);
23
24 // Calculate middle elements by summing adjacent elements from previous row
25 List<Integer> previousRow = pascalTriangle.get(rowIndex);
26 for (int colIndex = 1; colIndex < previousRow.size(); colIndex++) {
27 int sum = previousRow.get(colIndex - 1) + previousRow.get(colIndex);
28 currentRow.add(sum);
29 }
30
31 // Last element of each row is always 1
32 currentRow.add(1);
33
34 // Add the completed row to Pascal's Triangle
35 pascalTriangle.add(currentRow);
36 }
37
38 return pascalTriangle;
39 }
40}
41
1class Solution {
2public:
3 vector<vector<int>> generate(int numRows) {
4 // Initialize the result vector to store Pascal's triangle
5 vector<vector<int>> triangle;
6
7 // Add the first row [1] to the triangle
8 triangle.push_back(vector<int>(1, 1));
9
10 // Generate remaining rows (from row 2 to numRows)
11 for (int rowIndex = 0; rowIndex < numRows - 1; ++rowIndex) {
12 // Create a new row for the current level
13 vector<int> currentRow;
14
15 // First element of each row is always 1
16 currentRow.push_back(1);
17
18 // Calculate middle elements by summing adjacent elements from previous row
19 for (int colIndex = 1; colIndex < triangle[rowIndex].size(); ++colIndex) {
20 int sum = triangle[rowIndex][colIndex - 1] + triangle[rowIndex][colIndex];
21 currentRow.push_back(sum);
22 }
23
24 // Last element of each row is always 1
25 currentRow.push_back(1);
26
27 // Add the completed row to the triangle
28 triangle.push_back(currentRow);
29 }
30
31 return triangle;
32 }
33};
34
1/**
2 * Generates Pascal's Triangle with the specified number of rows
3 * @param numRows - The number of rows to generate in Pascal's Triangle
4 * @returns A 2D array representing Pascal's Triangle
5 */
6function generate(numRows: number): number[][] {
7 // Initialize the result array with the first row [1]
8 const pascalTriangle: number[][] = [[1]];
9
10 // Generate each subsequent row
11 for (let rowIndex = 0; rowIndex < numRows - 1; rowIndex++) {
12 // Start each new row with 1
13 const currentRow: number[] = [1];
14
15 // Calculate middle elements by summing adjacent elements from the previous row
16 for (let colIndex = 1; colIndex < pascalTriangle[rowIndex].length; colIndex++) {
17 const sum = pascalTriangle[rowIndex][colIndex - 1] + pascalTriangle[rowIndex][colIndex];
18 currentRow.push(sum);
19 }
20
21 // End each row with 1
22 currentRow.push(1);
23
24 // Add the completed row to Pascal's Triangle
25 pascalTriangle.push(currentRow);
26 }
27
28 return pascalTriangle;
29}
30
Time and Space Complexity
Time Complexity: O(n²)
, where n
is the number of rows (numRows
).
The outer loop runs numRows - 1
times, which is O(n)
. For each iteration i
, the inner list comprehension with pairwise
processes the previous row which has length i + 1
. The pairwise
function iterates through i
pairs, and the list comprehension creates i
sums. Therefore, the total work is:
- Row 1: 0 operations (base case)
- Row 2: 1 operation
- Row 3: 2 operations
- ...
- Row n: n-1 operations
Total operations: 0 + 1 + 2 + ... + (n-1) = n(n-1)/2 = O(n²)
Space Complexity: O(1)
(excluding the output array)
If we don't count the space required for the output list f
(which is necessary to return the result), the additional space used is:
- Variable
g
: temporarily stores each new row before appending tof
- The
pairwise
iterator and list comprehension use constant extra space
Since g
is reassigned in each iteration and doesn't accumulate additional space, the auxiliary space complexity is O(1)
.
If we include the output array, the total space would be O(n²)
as Pascal's triangle with n
rows contains 1 + 2 + 3 + ... + n = n(n+1)/2
total elements.
Common Pitfalls
1. Off-by-One Error in Loop Range
A frequent mistake is incorrectly setting the loop range, either iterating too many or too few times:
Incorrect:
for row_index in range(numRows): # This creates numRows + 1 rows total!
current_row = [1]
# ... rest of logic
Why it's wrong: Since we start with triangle = [[1]]
(already containing the first row), iterating numRows
times would create numRows + 1
rows total.
Correct approach:
for row_index in range(numRows - 1): # Creates exactly numRows rows
2. Index Out of Bounds When Accessing Adjacent Elements
When summing adjacent pairs, accessing indices incorrectly can cause errors:
Incorrect:
for j in range(len(previous_row)): # Goes one index too far!
current_row.append(previous_row[j] + previous_row[j + 1])
Why it's wrong: When j
reaches the last valid index, j + 1
will be out of bounds.
Correct approach:
for j in range(len(previous_row) - 1): # Stops before the last element
current_row.append(previous_row[j] + previous_row[j + 1])
3. Forgetting Edge Cases
Not handling the case when numRows = 0
or numRows = 1
:
Incorrect:
def generate(self, numRows: int) -> List[List[int]]:
triangle = [[1]]
for row_index in range(numRows - 1):
# ... logic
return triangle
Why it's wrong: While this works for numRows >= 1
, if the problem allows numRows = 0
, this would return [[1]]
instead of an empty list.
Correct approach:
def generate(self, numRows: int) -> List[List[int]]:
if numRows == 0:
return []
triangle = [[1]]
# ... rest of logic
4. Building Rows Incorrectly
Trying to modify the previous row or using incorrect logic for middle elements:
Incorrect:
current_row = previous_row.copy() # Wrong starting point
for j in range(1, len(current_row)):
current_row[j] = previous_row[j-1] + previous_row[j]
current_row.append(1)
Why it's wrong: This tries to modify a copy of the previous row, but the logic doesn't correctly handle the fact that each new row has one more element than the previous row.
Correct approach: Build the new row from scratch with the proper structure: [1] + [middle sums] + [1]
A person thinks of a number between 1 and 1000. You may ask any number questions to them, provided that the question can be answered with either "yes" or "no".
What is the minimum number of questions you needed to ask so that you are guaranteed to know the number that the person is thinking?
Recommended Readings
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!