Before attempting this problem, you should be comfortable with:
Dynamic Programming - Building each row from the previous row, using both top-down (recursive) and bottom-up (iterative) approaches
Recursion - Understanding how to compute a row by first computing all previous rows
Combinatorics - Recognizing that Pascal's Triangle values are binomial coefficients C(n, k) and computing them efficiently
1. Dynamic Programming (Top-Down)
Intuition
To get row n, we need row n - 1 first. Each row depends on the previous one, with interior elements being the sum of two adjacent elements from above. Recursion naturally models this dependency: we compute the previous row, then build the current row from it.
Algorithm
Base case: If rowIndex == 0, return [1].
Recursively get the previous row.
Start the current row with [1].
For each index from 1 to rowIndex - 1:
Add prevRow[i - 1] + prevRow[i] to the current row.
We build the entire triangle iteratively from row 0 up to the target row. Each row is constructed using values from the previous row. Though we store all rows, we only need the last one as our answer.
Algorithm
Create a 2D list where row i has i + 1 elements, all initialized to 1.
publicclassSolution{publicIList<int>GetRow(int rowIndex){var res =newList<int[]>();for(int i =0; i <= rowIndex; i++){int[] row =newint[i +1];
Array.Fill(row,1);for(int j =1; j < i; j++){
row[j]= res[i -1][j -1]+ res[i -1][j];}
res.Add(row);}return res[rowIndex].ToList();}}
funcgetRow(rowIndex int)[]int{
res :=make([][]int, rowIndex+1)for i :=0; i <= rowIndex; i++{
res[i]=make([]int, i+1)for j :=0; j <= i; j++{
res[i][j]=1}for j :=1; j < i; j++{
res[i][j]= res[i-1][j-1]+ res[i-1][j]}}return res[rowIndex]}
class Solution {fungetRow(rowIndex: Int): List<Int>{val res = mutableListOf<MutableList<Int>>()for(i in0..rowIndex){val row =MutableList(i +1){1}for(j in1 until i){
row[j]= res[i -1][j -1]+ res[i -1][j]}
res.add(row)}return res[rowIndex]}}
classSolution{funcgetRow(_ rowIndex:Int)->[Int]{var res =[[Int]]()for i in0...rowIndex {var row =[Int](repeating:1, count: i +1)for j in1..<i {
row[j]= res[i -1][j -1]+ res[i -1][j]}
res.append(row)}return res[rowIndex]}}
Time & Space Complexity
Time complexity: O(n2)
Space complexity: O(n2)
3. Dynamic Programming (Space Optimized - I)
Intuition
We only need the previous row to compute the current row, so we don't need to store the entire triangle. We keep one row at a time and build the next row by adding contributions from adjacent elements.
Algorithm
Start with res = [1].
For each iteration from 0 to rowIndex - 1:
Create nextRow of size len(res) + 1, filled with 0.
For each element in the current row, add its value to nextRow[j] and nextRow[j + 1].
classSolution:defgetRow(self, rowIndex:int)-> List[int]:
res =[1]for i inrange(rowIndex):
next_row =[0]*(len(res)+1)for j inrange(len(res)):
next_row[j]+= res[j]
next_row[j +1]+= res[j]
res = next_row
return res
publicclassSolution{publicList<Integer>getRow(int rowIndex){List<Integer> res =newArrayList<>();
res.add(1);for(int i =0; i < rowIndex; i++){List<Integer> nextRow =newArrayList<>(Collections.nCopies(res.size()+1,0));for(int j =0; j < res.size(); j++){
nextRow.set(j, nextRow.get(j)+ res.get(j));
nextRow.set(j +1, nextRow.get(j +1)+ res.get(j));}
res = nextRow;}return res;}}
classSolution{public:
vector<int>getRow(int rowIndex){
vector<int> res ={1};for(int i =0; i < rowIndex; i++){
vector<int>nextRow(res.size()+1,0);for(int j =0; j < res.size(); j++){
nextRow[j]+= res[j];
nextRow[j +1]+= res[j];}
res = nextRow;}return res;}};
classSolution{/**
* @param {number} rowIndex
* @return {number[]}
*/getRow(rowIndex){let res =[1];for(let i =0; i < rowIndex; i++){const nextRow =Array(res.length +1).fill(0);for(let j =0; j < res.length; j++){
nextRow[j]+= res[j];
nextRow[j +1]+= res[j];}
res = nextRow;}return res;}}
publicclassSolution{publicIList<int>GetRow(int rowIndex){var res =newList<int>{1};for(int i =0; i < rowIndex; i++){var nextRow =newList<int>(newint[res.Count +1]);for(int j =0; j < res.Count; j++){
nextRow[j]+= res[j];
nextRow[j +1]+= res[j];}
res = nextRow;}return res;}}
funcgetRow(rowIndex int)[]int{
res :=[]int{1}for i :=0; i < rowIndex; i++{
nextRow :=make([]int,len(res)+1)for j :=0; j <len(res); j++{
nextRow[j]+= res[j]
nextRow[j+1]+= res[j]}
res = nextRow
}return res
}
class Solution {fungetRow(rowIndex: Int): List<Int>{var res =mutableListOf(1)for(i in0 until rowIndex){val nextRow =MutableList(res.size +1){0}for(j in res.indices){
nextRow[j]+= res[j]
nextRow[j +1]+= res[j]}
res = nextRow
}return res
}}
classSolution{funcgetRow(_ rowIndex:Int)->[Int]{var res =[1]for_in0..<rowIndex {var nextRow =[Int](repeating:0, count: res.count +1)for j in0..<res.count {
nextRow[j]+= res[j]
nextRow[j +1]+= res[j]}
res = nextRow
}return res
}}
Time & Space Complexity
Time complexity: O(n2)
Space complexity: O(n)
4. Dynamic Programming (Space Optimized - II)
Intuition
We can update the row in place by iterating from right to left. This ensures we don't overwrite values we still need. Each element becomes the sum of itself and the element to its left, which matches how Pascal's Triangle is built.
Algorithm
Initialize row with rowIndex + 1 elements, all set to 1.
publicclassSolution{publicIList<int>GetRow(int rowIndex){var row =newList<int>(newint[rowIndex +1]);for(int i =0; i <= rowIndex; i++) row[i]=1;for(int i =1; i < rowIndex; i++){for(int j = i; j >0; j--){
row[j]+= row[j -1];}}return row;}}
funcgetRow(rowIndex int)[]int{
row :=make([]int, rowIndex+1)for i :=range row {
row[i]=1}for i :=1; i < rowIndex; i++{for j := i; j >0; j--{
row[j]+= row[j-1]}}return row
}
class Solution {fungetRow(rowIndex: Int): List<Int>{val row =MutableList(rowIndex +1){1}for(i in1 until rowIndex){for(j in i downTo 1){
row[j]+= row[j -1]}}return row
}}
classSolution{funcgetRow(_ rowIndex:Int)->[Int]{var row =[Int](repeating:1, count: rowIndex +1)for i in1..<rowIndex {for j instride(from: i, through:1, by:-1){
row[j]+= row[j -1]}}return row
}}
Time & Space Complexity
Time complexity: O(n2)
Space complexity: O(n)
5. Combinatorics
Intuition
The values in row n of Pascal's Triangle are the binomial coefficients C(n, 0), C(n, 1), ..., C(n, n). We can compute each coefficient incrementally from the previous one using the formula: C(n, k) = C(n, k - 1) * (n - k + 1) / k.
Algorithm
Start with row = [1].
For each i from 1 to rowIndex:
Compute the next value as row[last] * (rowIndex - i + 1) / i.
classSolution:defgetRow(self, rowIndex:int)-> List[int]:
row =[1]for i inrange(1, rowIndex +1):
row.append(row[-1]*(rowIndex - i +1)// i)return row
publicclassSolution{publicList<Integer>getRow(int rowIndex){List<Integer> row =newArrayList<>();
row.add(1);for(int i =1; i <= rowIndex; i++){
row.add((int)((long)row.get(row.size()-1)*(rowIndex - i +1)/ i));}return row;}}
classSolution{public:
vector<int>getRow(int rowIndex){
vector<int> row ={1};for(int i =1; i <= rowIndex; i++){
row.push_back(int(row.back()*1LL*(rowIndex - i +1)/ i));}return row;}};
classSolution{/**
* @param {number} rowIndex
* @return {number[]}
*/getRow(rowIndex){const row =[1];for(let i =1; i <= rowIndex; i++){
row.push(
Math.floor((row[row.length -1]*(rowIndex - i +1))/ i),);}return row;}}
publicclassSolution{publicIList<int>GetRow(int rowIndex){var row =newList<int>{1};for(int i =1; i <= rowIndex; i++){
row.Add((int)((long)row[row.Count -1]*(rowIndex - i +1)/ i));}return row;}}
funcgetRow(rowIndex int)[]int{
row :=[]int{1}for i :=1; i <= rowIndex; i++{
row =append(row, row[len(row)-1]*(rowIndex-i+1)/i)}return row
}
class Solution {fungetRow(rowIndex: Int): List<Int>{val row =mutableListOf(1)for(i in1..rowIndex){
row.add((row.last().toLong()*(rowIndex - i +1)/ i).toInt())}return row
}}
classSolution{funcgetRow(_ rowIndex:Int)->[Int]{var row =[1]for i in1...rowIndex {
row.append(row.last!*(rowIndex - i +1)/ i)}return row
}}
Time & Space Complexity
Time complexity: O(n)
Space complexity: O(n)
Common Pitfalls
Integer Overflow in Combinatorics Approach
When computing binomial coefficients using the formula C(n, k) = C(n, k-1) * (n - k + 1) / k, the intermediate multiplication can overflow before the division. Always multiply first and divide immediately, or use long types to hold intermediate results before casting back to int.
Wrong Iteration Direction in Space-Optimized DP
When updating the row in place, iterating from left to right overwrites values that are still needed for subsequent calculations. You must iterate from right to left so that each element is updated using the original (unmodified) values from the previous logical row.