【简单】118. 杨辉三角

0 阅读3分钟

给定一个非负整数 numRows 生成「杨辉三角」的前 numRows 行。

在**「杨辉三角」**中,每个数是它左上方和右上方的数的和。

示例 1:

输入: numRows = 5
输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]

示例 2:

输入: numRows = 1
输出: [[1]]

提示:

  • 1 <= numRows <= 30

1. 生活案例:家族财富的传承

想象一个非常公平的家族遗产继承规则:

  • 规则

    • 每一代的第一个孩子最后一个孩子,都只能继承 1 万元(固定资产)。
    • 中间的每一个孩子,其财富值等于他 “亲生父母” (上一代左上方和右上方的那两个人)财富的总和。
  • 例子

    • 第一代:只有一个人,拿 1 万。
    • 第二代:两个边上的孩子,各拿 1 万。
    • 第三代:中间那个孩子,财富等于第二代那两个人的和:1+1=21 + 1 = 2 万。
    • 第四代:中间两个孩子,财富分别等于上一代相邻两人的和:1+2=31+2=3 万,以及 2+1=32+1=3 万。

2. 代码解析与“生活化”注释

这段代码展示了如何利用“上一行”的结果,递推出“当前行”的每一个数。

JavaScript

/**
 * @param {number} numRows - 我们要生成的家族总代数
 * @return {number[][]} - 完整的财富分布图
 */
var generate = function(numRows) {
    let res = []; // 用来装每一代人的数组

    // 外层循环:i 代表当前的代数(从第 0 代到第 numRows-1 代)
    for (let i = 0; i < numRows; i++) {
        // 每一行先预填充 1。第 i 行就有 i+1 个孩子。
        // 生活化解释:先把每个人的初始财富设为 1(两头的孩子以后就不动了)
        let row = new Array(i + 1).fill(1);

        // 内层循环:处理中间的孩子(下标从 1 到 i-1)
        // 如果是两头的孩子 (j=0 或 j=i),直接跳过,保持为 1
        for (let j = 1; j < i; j++) {
            // 生活化解释:
            // 中间这个孩子 row[j] 的财富,来自他上一代 (res[i-1]) 的两个爹妈
            // 一个爹妈在左上方 (j-1),另一个在右上方 (j)
            row[j] = res[i - 1][j - 1] + res[i - 1][j];
        }

        // 这一代人的财富分配好了,加入家族总表
        res.push(row);
    }

    return res;
};

3. 为什么代码这样写?(逻辑结构)

  1. 分层存储res 是一个二维数组,每一项代表一行。这种结构非常适合 DP,因为 res[i] 的结果高度依赖于 res[i-1]

  2. 边界处理:代码巧妙地用了 fill(1)。这样我们就不用专门写 if (j == 0 || j == i) 来处理每行开头和结尾的 1 了,只需要通过内层循环修改中间的数值。

  3. 时空效率

    • 时间:我们要填满整个三角形,总共有约 numRows2/2numRows^2 / 2 个数字,所以复杂度是 O(numRows2)O(numRows^2)
    • 空间:除了存储结果,我们没有用到额外的复杂结构。

总结

杨辉三角是动态规划中最直观的 “状态转移”

  • 当前状态row[j]
  • 转移方程row[j] = 上行左侧 + 上行右侧