题目链接
思路
用一个矩阵,按「杨辉三角」的规则,依次算出每行,直到算到第 rowIndex 行,返回该行即可。
步骤
以 rowIndex = 4 为例,展示整个过程。
1. 初始化矩阵
初始化一个 (rowIndex+1)*(rowIndex+1) 规格的矩阵。
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
2. 有效区域的首尾置成1
有效区域即杨辉三角的放置区域,此处左对齐。
1 0 0 0 0
1 1 0 0 0
1 0 1 0 0
1 0 0 1 0
1 0 0 0 1
3. 计算每行除去首尾后的剩余位置
每行的首尾已经填充成1,根据杨辉三角的计算规则,剩余位置j = 上行的j + 上行的j-1。
1 0 0 0 0
1 1 0 0 0
1 2 1 0 0
1 3 3 1 0
1 4 6 4 1
4. 得出结果
返回矩阵的最后 1 行,即是杨辉三角的第 rowIndex 行。
复杂度
假设矩阵的规格是 N*N。
时间复杂度
本解法中,矩阵的普遍位置的计算次数是 N + N-1 + N-2 + ... + 1, 根据等差数列的求和公式,得到最终公式是 N * 1 + 1/2 * N * (N-1) * 1, 最后表达式的最高阶项是N^2, 所以本解法的时间复杂度是O(N^2)。
额外空间复杂度
因为用了一个 N*N 的矩阵,所以额外空间复杂度是 O(N^2)。
代码
TypeScript
function getRow(rowIndex: number): number[] {
// 初始化矩阵, 规格是 (rowIndex+1)*(rowIndex+1)
// 这样就可以用 C[rowIndex] 代表杨辉三角的第 rowIndex 行
const C = new Array(rowIndex+1).fill(0);
// 遍历矩阵
for (let i = 0; i <= rowIndex; i++) {
// 每行先初始化一个全是0的数组, 注意这些数组长度都是 rowIndex+1。
C[i] = new Array(rowIndex+1).fill(0);
// 每行数组,有效区域的首尾,都置成1 (有效区域即杨辉三角区域)
C[i][0] = C[i][i] = 1;
// 每行中, 除去有效区域的首尾,每一个剩余的位置j = 上行j + 上行j-1
for (let j = 1; j < i; j++) {
C[i][j] = C[i-1][j-1] + C[i-1][j];
}
// 一直走到 rowIndex 行, 矩阵就填充完毕 1 个杨辉三角部分了
}
// 要返回矩阵的rowIndex行
return C[rowIndex];
}
C++
class Solution {
public:
vector<int> getRow(int rowIndex) {
// 矩阵的行数,也就是杨辉三角的行数
int rows = rowIndex+1;
// 新建 1 个矩阵
vector<vector<int>> C(rows);
// 遍历矩阵
for (int i = 0; i < rows; i++) {
// 修改矩阵每一行的元素个数
C[i].resize(i+1);
// 设置每一行的首尾的元素为 1
C[i][0] = C[i][i] = 1;
// 每行除了首尾,普遍位置j = 上一行的j + 上一行的j-1
for (int j = 1; j < i; j++) {
C[i][j] = C[i-1][j] + C[i-1][j-1];
}
}
// 矩阵最后一行即是`杨辉三角的第rowIndex行`
return C[rowIndex];
}
};
进阶解法 额外空间复杂度降为O(N)
我们不需要创建出整个矩阵,用 2 个数组交替计算即可,每个数组的长度最多是 rowIndex, 将 rowIndex 记为 N, 这样额外空间复杂度就降低为O(N)。
TypeScript
function getRow(rowIndex: number): number[] {
let lastArray: Array<number> = [];
let thisArray: Array<number> = [];
for (let i = 0; i < rowIndex+1; i++) {
const len = i+1;
thisArray = new Array(len).fill(0);
thisArray[0] = thisArray[len-1] = 1;
for (let j = 1; j < len-1; j++) {
thisArray[j] = lastArray[j] + lastArray[j-1];
}
lastArray = thisArray;
}
return thisArray;
};
再次进阶 额外空间复杂度降为O(1)
只用 1 个数组,返回的数组不算在额外空间里,所以额外空间复杂度O(1)。
每行都从最后一个数开始往前推,由于numbers[j]等于上一行的j+上一行的j-1,只有一个数组的情况下,上一行的j就是numbers[j],上一行的j-1由于还没轮到它计算,所以值就保存在numbers[j-1]。
这样一来,每次推导都是 numbers[j] = numbers[j] + numbers[j-1]。
递推过程
这些值都在一个数组numbers里,每次计算都是从右往左推,0位置初始化为1并且始终为1,不需要推导。
0 0 0 0 0 初始化
1 0 0 0 0 初始化,以便第 1 次推导
1 1 0 0 0 推导最后 1 个 (1=0+1)
1 2 1 0 0 推导最后 2 个 (1=0+1, 2=1+1)
1 3 3 1 0 推导最后 3 个 (1=0+1, 3=1+2, 3=2+1)
1 4 6 4 1 推导最后 4 个 (1=0+1, 4=1+3, 6=3+3, 4=3+1)
TypeScript
function getRow(rowIndex: number): number[] {
const numbers = new Array(rowIndex+1).fill(0);
numbers[0] = 1;
for (let i = 1; i <= rowIndex; i++) {
for (let j = i; j > 0; j--) {
numbers[j] = numbers[j] + numbers[j-1];
}
}
return numbers;
};