Pascal's Triangle II
EasyPrompt
Given an integer rowIndex, return the rowIndexth (0-indexed) row of the Pascal's triangle.
In Pascal's triangle, each number is the sum of the two numbers directly above it as shown:
Example 1:
Input: rowIndex = 3
Output: [1,3,3,1]Example 2:
Input: rowIndex = 0
Output: [1]Example 3:
Input: rowIndex = 1
Output: [1,1]
Constraints:
0 <= rowIndex <= 33
Follow up: Could you optimize your algorithm to use only O(rowIndex) extra space?
Approaches
4 approaches with complexity analysis and trade-offs.
This approach directly translates the recursive definition of Pascal's Triangle, C(n, k) = C(n-1, k-1) + C(n-1, k), into a recursive function. To get the entire rowIndex-th row, we call this function for each column index from 0 to rowIndex.
Algorithm
- Create an empty list
resultto store the row. - Loop with an index
kfrom0torowIndex. - In each iteration, call a recursive helper function,
pascalValue(rowIndex, k), to compute the element at that position. - Add the value returned by the helper function to the
resultlist. - The
pascalValue(row, col)helper function is defined as follows:- Base Case: If
colis0orcolis equal torow, return1. - Recursive Step: Otherwise, return the sum of
pascalValue(row - 1, col - 1)andpascalValue(row - 1, col).
- Base Case: If
Walkthrough
We define a recursive function, say pascalValue(row, col), which computes the value at a specific position in the triangle.
The base cases for the recursion are when col == 0 or col == row, in which case the value is 1.
For any other position (row, col), the value is the sum of pascalValue(row - 1, col - 1) and pascalValue(row - 1, col).
To generate the final result, we create a list and iterate from k = 0 to rowIndex, populating the list with the results of pascalValue(rowIndex, k).
This method is highly inefficient because it recomputes the same values multiple times. For example, calculating the value at (5, 2) and (5, 3) both require computing the value at (4, 2), leading to an exponential number of overlapping subproblems.
class Solution { public List<Integer> getRow(int rowIndex) { List<Integer> row = new ArrayList<>(); for (int k = 0; k <= rowIndex; k++) { row.add(pascalValue(rowIndex, k)); } return row; } private int pascalValue(int row, int col) { if (col == 0 || col == row) { return 1; } // Recursive step return pascalValue(row - 1, col - 1) + pascalValue(row - 1, col); }}Complexity
Time
O(2^rowIndex)
Space
O(rowIndex)
Trade-offs
Pros
Simple to understand as it directly models the mathematical definition of Pascal's Triangle.
Cons
Extremely inefficient due to a massive number of redundant computations.
Will likely result in a 'Time Limit Exceeded' error for even moderately large values of
rowIndex.
This approach builds the entire Pascal's Triangle row by row, up to the desired rowIndex. It stores all the generated rows in a list of lists and finally returns the last one.
Algorithm
- Initialize a list of lists,
triangle. - Add the first row,
[1], totriangle. - Loop with an index
ifrom1torowIndex.- In each iteration, get the previous row
prevRowfromtriangle.get(i-1). - Create a new list
currentRow. - Add
1as the first element ofcurrentRow. - Loop with an index
jfrom1toi-1.- Calculate the sum
prevRow.get(j-1) + prevRow.get(j)and add it tocurrentRow.
- Calculate the sum
- Add
1as the last element ofcurrentRow. - Add
currentRowto thetriangle.
- In each iteration, get the previous row
- After the loops complete, return the list at index
rowIndexfromtriangle.
Walkthrough
Instead of recomputing values, we can use dynamic programming. We build the triangle from the top down. We start by creating the first row [1]. Then, for each subsequent row i, we generate its elements using the values from the previously computed row i-1. The j-th element of row i is the sum of the (j-1)-th and j-th elements of row i-1. We store all these rows in a list of lists. While this is much more efficient than the recursive approach, it uses more space than required.
class Solution { public List<Integer> getRow(int rowIndex) { List<List<Integer>> triangle = new ArrayList<>(); // First row triangle.add(new ArrayList<>()); triangle.get(0).add(1); for (int i = 1; i <= rowIndex; i++) { List<Integer> row = new ArrayList<>(); List<Integer> prevRow = triangle.get(i - 1); row.add(1); // First element of each row is 1 for (int j = 1; j < i; j++) { row.add(prevRow.get(j - 1) + prevRow.get(j)); } row.add(1); // Last element of each row is 1 triangle.add(row); } return triangle.get(rowIndex); }}Complexity
Time
O(rowIndex^2)
Space
O(rowIndex^2)
Trade-offs
Pros
Avoids redundant calculations present in the recursive approach.
Conceptually straightforward to implement.
Cons
Uses excessive space by storing all rows of the triangle, which is not necessary for this problem.
This approach improves upon the previous DP method by realizing that to compute the current row, we only need the previous row. This allows us to reduce the space complexity significantly by using only a single list and updating it in-place.
Algorithm
- Initialize a list
rowwith the first element1. - Loop with an index
ifrom1torowIndex. This outer loop corresponds to building each row from 1 torowIndex. - Inside the loop, iterate backwards from
j = i - 1down to1.- Update the element at index
jby adding the element atj-1to it:row.set(j, row.get(j) + row.get(j-1)).
- Update the element at index
- After the inner loop, append a
1to the end of the listrow. This completes thei-th row. - After the outer loop finishes,
rowwill hold therowIndex-th row of Pascal's triangle.
Walkthrough
We can optimize the space complexity of the DP approach to O(rowIndex). We only need to store one row at a time. To compute the next row, we can modify the current row in-place. The key insight is to perform the updates from right to left. If we update from left to right, we would overwrite values that are still needed for subsequent calculations in the same row. By iterating backwards, we use the 'old' values from the previous row's computation before they are updated.
For each new row i, we first conceptually have the (i-1)-th row. The i-th row will have one more element. We can add a 1 at the end and then update the values from right to left.
class Solution { public List<Integer> getRow(int rowIndex) { List<Integer> row = new ArrayList<>(); row.add(1); // Corresponds to row 0 for (int i = 1; i <= rowIndex; i++) { // Update existing elements from right to left for (int j = i - 1; j > 0; j--) { row.set(j, row.get(j) + row.get(j - 1)); } // Add the last element for the new row row.add(1); } return row; }}Complexity
Time
O(rowIndex^2)
Space
O(rowIndex)
Trade-offs
Pros
Very space-efficient, using only
O(rowIndex)extra space, which satisfies the follow-up question.Maintains the simplicity of the DP approach.
Cons
The time complexity is still quadratic, which is not optimal.
This is the most efficient approach. It leverages the mathematical formula for Pascal's Triangle elements. The element at row n and column k is the binomial coefficient C(n, k). We can calculate each element of the row iteratively in a single pass.
Algorithm
- Create an empty list
row. - Initialize a
longvariableval = 1. This will hold the current coefficient value. - Loop with an index
kfrom0torowIndex.- Add the current
val(cast to anint) to therowlist. - Update
valfor the next iteration using the formula:val = val * (rowIndex - k) / (k + 1).
- Add the current
- Return the
rowlist.
Walkthrough
The k-th element (0-indexed) of the n-th row is given by the binomial coefficient C(n, k). Instead of calculating factorials, which can lead to overflow, we can use the multiplicative formula which relates adjacent coefficients: C(n, k) = C(n, k-1) * (n - k + 1) / k.
We can generate the entire rowIndex-th row in a single pass. We start with the first element, C(rowIndex, 0), which is 1. Then, we loop from k = 1 to rowIndex, calculating each subsequent element using the value of the previous element and the formula above. This avoids nested loops and results in a linear time complexity.
class Solution { public List<Integer> getRow(int rowIndex) { List<Integer> row = new ArrayList<>(rowIndex + 1); long val = 1; for (int k = 0; k <= rowIndex; k++) { row.add((int) val); // Calculate the next value in the row // C(n, k+1) = C(n, k) * (n-k) / (k+1) val = val * (rowIndex - k) / (k + 1); } return row; }}Complexity
Time
O(rowIndex)
Space
O(rowIndex)
Trade-offs
Pros
Optimal time complexity of
O(rowIndex).Optimal space complexity of
O(rowIndex)(required for the output).
Cons
Requires knowledge of the mathematical properties of binomial coefficients.
Care must be taken to use a
longfor intermediate calculations to prevent overflow, even if the final result fits in anint.
Solutions
Solution
class Solution {public List<Integer> getRow(int rowIndex) { List<Integer> f = new ArrayList<>(); for (int i = 0; i < rowIndex + 1; ++i) { f.add(1); } for (int i = 2; i < rowIndex + 1; ++i) { for (int j = i - 1; j > 0; --j) { f.set(j, f.get(j) + f.get(j - 1)); } } return f; }}/** * @param {number} rowIndex * @return {number[]} */ var getRow = function ( rowIndex,) { const f = Array(rowIndex + 1).fill(1); for (let i = 2; i < rowIndex + 1; ++i) { for (let j = i - 1; j; --j) { f[j] += f[j - 1]; } } return f;};class Solution {public: vector<int> getRow(int rowIndex) { vector<int> f(rowIndex + 1, 1); for (int i = 2; i < rowIndex + 1; ++i) { for (int j = i - 1; j; --j) { f[j] += f[j - 1]; } } return f; }};class Solution: def getRow(self, rowIndex: int) -> List[int]: f = [1] * (rowIndex + 1) for i in range(2, rowIndex + 1): for j in range(i - 1, 0, - 1): f[j] += f[j - 1] return fVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.