118. Pascal's Triangle
Problem Description
Given an integer numRows
, the task is to return the first numRows
of Pascal's triangle. In Pascal's triangle, each number is the sum of the two numbers directly above it except for the boundaries, which are always 1
. The triangle starts with a 1
at the top. Then, each subsequent row contains one more number than the previous row, and these numbers are positioned such that they form a triangle. The challenge here is to compute these numbers using the given property of Pascal's triangle and to represent the triangle in the form of a list of lists, where each inner list corresponds to a row in the triangle.
For example, the first 3 rows of Pascal's triangle look like this:
1 //First row (only one element) 1 1 //Second row (two 1s at the boundaries) 1 2 1 //Third row (1s at the boundaries, middle is sum of the two numbers above)
Intuition
The solution to generating Pascal's triangle is a direct application of its properties. The key idea is to start with the first row [1]
, and then generate each following row based on the previous row. For any new row, aside from the first and last elements which are 1
, each element is obtained by adding the two numbers directly above it in the triangle. With this approach, we can iteratively construct each row and add it to the final result.
This can be implemented by initializing a list with the first row as its only element, then using a loop to construct each subsequent row. Inside the loop, we can use list comprehension to generate the middle values of the row by summing pairs of consecutive elements from the last row. We use the pairwise
function (assuming Python 3.10+) to obtain the pairs, and then enclose the generated middle values with [1]
on both ends to complete the row. After constructing each row, we append it to our result list. We repeat this process numRows - 1
times since the first row is already initialized.
The Python code encapsulates this logic in the generate
function, with f
holding the result list of lists and g
being the new row being constructed each iteration. Finally, the function returns f
after the completion of the loop.
Learn more about Dynamic Programming patterns.
Solution Approach
In the provided code, the problem is approached by considering each row in Pascal's Triangle to be a list and the entire triangle to be a list of these lists.
The main steps of the implementation are as follows:
-
Initialize a list,
f
, with a single element that is the first row of Pascal's Triangle, which is simply[1]
. -
Loop from
0
tonumRows - 1
(the-1
is because we already have the first row). This loop will build one row at a time to add to ourf
list. -
For each iteration, construct a new row,
g
. The first element is always1
. This is done by simply specifying[1]
at the start. -
The middle elements of the row
g
are created by taking pairs of consecutive elements from the last row available inf
and summing them up. This is done using list comprehension and thepairwise
function.pairwise
takes an iterable and returns an iterator over pairs of consecutive elements. For example,pairwise([1,2,3])
would produce(1, 2), (2, 3)
.In the code, we have
[a + b for a, b in pairwise(f[-1])]
, which sums each pair of consecutive elements from the last rowf[-1]
to create the middle elements of the new rowg
. -
After the middle elements are calculated,
[1]
is appended to the end of the list to create the last element of the row, which is also1
. -
Row
g
is then added to the listf
. -
This process repeats until all
numRows
rows are generated. -
Finally, the list
f
is returned, which contains the representation of Pascal's Triangle up tonumRows
.
This solution uses nested lists to represent Pascal's Triangle, list comprehension to generate each row, and a loop to build the triangle one row at a time. The pairwise
function streamlines the process of summing adjacent elements from the previous row to build the interior of each new row.
Here is the critical part of the Python solution:
f = [[1]] # Initial Pascal's Triangle with the first row.
for i in range(numRows - 1):
g = [1] + [a + b for a, b in pairwise(f[-1])] + [1] # Constructing the new row
f.append(g) # Appending the new row to the triangle
return f # The complete Pascal's Triangle
The algorithm has a time complexity of O(n^2), where n is the number of rows, since each element of the triangle is computed exactly once.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with numRows = 5
to illustrate the generation of Pascal's Triangle:
-
We start with the initial list
f
which already contains the first row[1]
. -
We begin the loop with
i
ranging from0
tonumRows - 1
, which is0
to4
in this case. Remember, the first row is already accounted for, so we effectively need to generate4
more rows. -
For
i = 0
, we construct the second row:g = [1] + [a + b for a, b in pairwise(f[-1])] + [1]
f[-1]
is[1]
, sopairwise(f[-1])
doesn't generate any pairs since there's only one element.- We add
[1]
at the beginning and end.
So,
g = [1] + [] + [1]
which is simply[1, 1]
. We append[1, 1]
tof
. -
Our list
f
now looks like[[1], [1, 1]]
. -
For
i = 1
, we construct the third row:f[-1]
is[1, 1]
,pairwise(f[-1])
yields one pair:(1, 1)
.- We then add the sums of these pairs to
[1]
and enclose with[1]
at the end.
So,
g = [1] + [1 + 1] + [1]
which gives us[1, 2, 1]
. We append[1, 2, 1]
tof
. - We then add the sums of these pairs to
-
Our list
f
now looks like[[1], [1, 1], [1, 2, 1]]
. -
For
i = 2
, constructing the fourth row:f[-1]
is[1, 2, 1]
,pairwise(f[-1])
gives(1, 2)
and(2, 1)
.- The sums are
[1 + 2, 2 + 1]
which is[3, 3]
.
So,
g = [1] + [3, 3] + [1]
which forms[1, 3, 3, 1]
. We append[1, 3, 3, 1]
tof
. - The sums are
-
Our list
f
now looks like[[1], [1, 1], [1, 2, 1], [1, 3, 3, 1]]
. -
For
i = 3
, the final loop to construct the fifth row:f[-1]
is[1, 3, 3, 1]
,pairwise(f[-1])
yields(1, 3)
,(3, 3)
, and(3, 1)
.- The sums are
[1 + 3, 3 + 3, 3 + 1]
which is[4, 6, 4]
.
g = [1] + [4, 6, 4] + [1]
, forming the row[1, 4, 6, 4, 1]
. We append this row tof
. - The sums are
-
Our final list
f
, representing the first5
rows of Pascal's Triangle, now looks like[[1], [1, 1], [1, 2, 1], [1, 3, 3, 1], [1, 4, 6, 4, 1]]
.
The function would then return this list f
.
Executing these steps with a proper function such as the given generate
function will yield the desired Pascal's Triangle up to the specified row. Each iteration builds upon the previous, aligning perfectly with the properties of Pascal's Triangle.
Solution Implementation
1class Solution:
2 def generate(self, numRows: int) -> List[List[int]]:
3 # Initialize the first row of Pascal's Triangle
4 pascal_triangle = [[1]]
5
6 # Generate each row of Pascal's Triangle
7 for i in range(1, numRows):
8 # The first element of each row is always 1
9 new_row = [1]
10
11 # Calculate the intermediate elements of the row
12 # by adding the pairs of adjacent elements from the previous row
13 for j in range(1, i):
14 sum_of_elements = pascal_triangle[i - 1][j - 1] + pascal_triangle[i - 1][j]
15 new_row.append(sum_of_elements)
16
17 # The last element of each row is also always 1
18 new_row.append(1)
19
20 # Append the newly generated row to Pascal's Triangle
21 pascal_triangle.append(new_row)
22
23 # Return the completed Pascal's Triangle
24 return pascal_triangle
25
1class Solution {
2 public List<List<Integer>> generate(int numRows) {
3 // Initialize the main list that will hold all rows of Pascal's Triangle
4 List<List<Integer>> triangle = new ArrayList<>();
5
6 // The first row of Pascal's Triangle is always [1]
7 triangle.add(List.of(1));
8
9 // Loop through each row (starting from the second row)
10 for (int rowIndex = 1; rowIndex < numRows; ++rowIndex) {
11 // Initialize the list to hold the current row's values
12 List<Integer> row = new ArrayList<>();
13
14 // The first element in each row is always 1
15 row.add(1);
16
17 // Compute the values within the row (excluding the first and last element)
18 for (int j = 0; j < triangle.get(rowIndex - 1).size() - 1; ++j) {
19 // Calculate each element as the sum of the two elements above it
20 row.add(triangle.get(rowIndex - 1).get(j) + triangle.get(rowIndex - 1).get(j + 1));
21 }
22
23 // The last element in each row is always 1
24 row.add(1);
25
26 // Add the computed row to the triangle list
27 triangle.add(row);
28 }
29
30 // Return the fully constructed list of rows of Pascal's Triangle
31 return triangle;
32 }
33}
34
1class Solution {
2public:
3 // Function to generate Pascal's Triangle with the given number of rows.
4 vector<vector<int>> generate(int numRows) {
5 // Initialize a 2D vector to hold the rows of Pascal's Triangle.
6 vector<vector<int>> pascalsTriangle;
7
8 // The first row of Pascal's Triangle is always [1].
9 pascalsTriangle.push_back(vector<int>(1, 1));
10
11 // Generate the subsequent rows of Pascal's Triangle.
12 for (int rowIndex = 1; rowIndex < numRows; ++rowIndex) {
13 // Initialize the new row starting with a '1'.
14 vector<int> newRow;
15 newRow.push_back(1);
16
17 // Fill in the values between the first and last '1' of the row.
18 for (int elementIndex = 0; elementIndex < pascalsTriangle[rowIndex - 1].size() - 1; ++elementIndex) {
19 // The new value is the sum of the two values directly above it.
20 newRow.push_back(pascalsTriangle[rowIndex - 1][elementIndex] + pascalsTriangle[rowIndex - 1][elementIndex + 1]);
21 }
22
23 // The last value in a row of Pascal's Triangle is always '1'.
24 newRow.push_back(1);
25
26 // Append the newly created row to Pascal's Triangle.
27 pascalsTriangle.push_back(newRow);
28 }
29
30 // Return the fully generated Pascal's Triangle.
31 return pascalsTriangle;
32 }
33};
34
1// Generates Pascal's Triangle with a given number of rows
2function generate(numRows: number): number[][] {
3 // Initialize the triangle with the first row
4 const triangle: number[][] = [[1]];
5
6 // Populate the triangle row by row
7 for (let rowIndex = 1; rowIndex < numRows; ++rowIndex) {
8 // Initialize the new row starting with '1'
9 const row: number[] = [1];
10
11 // Calculate each value for the current row based on the previous row
12 for (let j = 0; j < triangle[rowIndex - 1].length - 1; ++j) {
13 row.push(triangle[rowIndex - 1][j] + triangle[rowIndex - 1][j + 1]);
14 }
15
16 // End the current row with '1'
17 row.push(1);
18
19 // Add the completed row to the triangle
20 triangle.push(row);
21 }
22
23 // Return the fully generated Pascal's Triangle
24 return triangle;
25}
26
Time and Space Complexity
The provided code generates Pascal's Triangle with numRows
levels. Here is the analysis of its time and space complexity:
Time Complexity
The time complexity of the code can be understood by analyzing the operations inside the for-loop that generates each row of Pascal's Triangle.
- There are
numRows - 1
iterations since the first row is initialized before the loop. - Inside the loop,
pairwise(f[-1])
generates tuples of adjacent elements from the last row which takesO(j)
time, wherej
is the number of elements in the previous row (since every step inside the enumeration would be constant time). - The list comprehension
[a + b for a, b in pairwise(f[-1])]
performsj - 1
additions, so this is alsoO(j)
wherej
is the size of the previous row. - Appending the first and last
1
isO(1)
each for a total ofO(2)
which simplifies toO(1)
.
As the rows of Pascal's Triangle increase by one element each time, summing up the operations over all rows gives us a total time of approximately O(1)
for the first row, plus O(2) + O(3) + ... + O(numRows)
for the numRows - 1
remaining rows. This results in a time complexity of O(numRows^2)
.
Therefore, the overall time complexity is O(numRows^2)
.
Space Complexity
The space complexity is determined by the space required to store the generated rows of Pascal's Triangle.
f
is initialized with a single row containing one1
which we can considerO(1)
.- In each iteration of the loop, a new list with
i + 2
elements is created (since each new row has one more element than the previous one) and appended tof
.
Summing this up, the space required will be O(1)
for the first row, plus O(2) + O(3) + ... + O(numRows)
for the rest of the rows.
This results in a space complexity of O(numRows^2)
since the space required is proportional to the square of the number of rows due to the nature of Pascal's Triangle.
Therefore, the overall space complexity is O(numRows^2)
.
Learn more about how to find time and space complexity quickly using problem constraints.
Which data structure is used to implement recursion?
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
LeetCode 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 we
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 algomonster s3 us east 2 amazonaws com recursion jpg You first
Want a Structured Path to Master System Design Too? Don’t Miss This!