Facebook Pixel

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] where 2 = 1 + 1 (sum of the two elements above it)
  • Row 4: [1, 3, 3, 1] where 3 = 1 + 2 and 3 = 2 + 1
  • Row 5: [1, 4, 6, 4, 1] where 4 = 1 + 3, 6 = 3 + 3, and 4 = 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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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 always 1
  • For the third row [1, 2, 1], the middle element 2 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:

  1. Start with 1 (the left edge)
  2. Add all sums of adjacent pairs from the previous row
  3. 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 1s 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 row g based on the last row in our triangle f[-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], then pairwise(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 triangle f
  • 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 Evaluator

Example 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 to f
  • 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]

Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

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

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More