给定一个非负整数 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 万。
- 第三代:中间那个孩子,财富等于第二代那两个人的和: 万。
- 第四代:中间两个孩子,财富分别等于上一代相邻两人的和: 万,以及 万。
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. 为什么代码这样写?(逻辑结构)
-
分层存储:
res是一个二维数组,每一项代表一行。这种结构非常适合 DP,因为res[i]的结果高度依赖于res[i-1]。 -
边界处理:代码巧妙地用了
fill(1)。这样我们就不用专门写if (j == 0 || j == i)来处理每行开头和结尾的 1 了,只需要通过内层循环修改中间的数值。 -
时空效率:
- 时间:我们要填满整个三角形,总共有约 个数字,所以复杂度是 。
- 空间:除了存储结果,我们没有用到额外的复杂结构。
总结
杨辉三角是动态规划中最直观的 “状态转移” :
- 当前状态:
row[j] - 转移方程:
row[j] = 上行左侧 + 上行右侧