「力扣119」 杨辉三角 II

233 阅读2分钟

题目链接

杨辉三角 II

思路

用一个矩阵,按「杨辉三角」的规则,依次算出每行,直到算到第 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;
};