LeetCode 第118题:杨辉三角
题目描述
给定一个非负整数 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
- 对于其他位置的元素,其值等于上一行的相邻两个元素之和
具体步骤:
- 创建一个二维列表result来存储杨辉三角
- 对于第一行,只有一个元素1
- 对于后续的每一行i(从1开始): a. 创建一个新的列表row,长度为i+1 b. 设置row[0]和row[i]为1 c. 对于row中的其他位置j(从1到i-1),设置row[j] = result[i-1][j-1] + result[i-1][j] d. 将row添加到result中
- 返回result
时间复杂度:O(numRows²),需要生成numRows行,每行最多有numRows个元素 空间复杂度:O(numRows²),需要存储杨辉三角的所有元素
方法二:数学公式
杨辉三角的每一行实际上是二项式展开的系数,可以使用组合数公式直接计算。
关键点:
- 第n行第k个元素的值为组合数C(n,k),即C(n,k) = n! / (k! * (n-k)!)
- 可以利用递推公式C(n,k) = C(n,k-1) * (n-k+1) / k来优化计算
具体步骤:
- 创建一个二维列表result来存储杨辉三角
- 对于每一行i(从0开始): a. 创建一个新的列表row,长度为i+1 b. 设置row[0]为1 c. 对于row中的其他位置j(从1到i),使用递推公式计算row[j] = row[j-1] * (i-j+1) / j d. 将row添加到result中
- 返回result
时间复杂度:O(numRows²),需要生成numRows行,每行最多有numRows个元素 空间复杂度:O(numRows²),需要存储杨辉三角的所有元素
图解思路
杨辉三角的构造过程
| 行号 | 杨辉三角行 | 构造方式 |
|---|---|---|
| 1 | [1] | 第一行只有一个元素1 |
| 2 | [1,1] | 第二行的首尾元素为1 |
| 3 | [1,2,1] | 第三行的首尾元素为1,中间元素2 = 1+1(上一行的相邻元素之和) |
| 4 | [1,3,3,1] | 第四行的首尾元素为1,中间元素3 = 1+2,3 = 2+1(上一行的相邻元素之和) |
| 5 | [1,4,6,4,1] | 第五行的首尾元素为1,中间元素4 = 1+3,6 = 3+3,4 = 3+1(上一行的相邻元素之和) |
动态规划过程示意图
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
对于第5行的元素6,它等于第4行的相邻两个元素3和3的和:3 + 3 = 6。
代码实现
C# 实现
public class Solution {
// 方法一:动态规划
public IList<IList<int>> Generate(int numRows) {
IList<IList<int>> result = new List<IList<int>>();
// 处理第一行
result.Add(new List<int> { 1 });
// 处理后续行
for (int i = 1; i < numRows; i++) {
List<int> row = new List<int>();
IList<int> prevRow = result[i - 1];
// 每行的第一个元素总是1
row.Add(1);
// 计算中间元素
for (int j = 1; j < i; j++) {
row.Add(prevRow[j - 1] + prevRow[j]);
}
// 每行的最后一个元素总是1
row.Add(1);
result.Add(row);
}
return result;
}
// 方法二:数学公式
public IList<IList<int>> GenerateWithMath(int numRows) {
IList<IList<int>> result = new List<IList<int>>();
for (int i = 0; i < numRows; i++) {
List<int> row = new List<int>();
// 每行的第一个元素总是1
row.Add(1);
// 使用组合数公式计算中间元素
for (int j = 1; j <= i; j++) {
// C(i,j) = C(i,j-1) * (i-j+1) / j
row.Add(row[j - 1] * (i - j + 1) / j);
}
result.Add(row);
}
return result;
}
}
Python 实现
class Solution:
# 方法一:动态规划
def generate(self, numRows: int) -> List[List[int]]:
result = []
# 处理第一行
result.append([1])
# 处理后续行
for i in range(1, numRows):
row = []
prev_row = result[i - 1]
# 每行的第一个元素总是1
row.append(1)
# 计算中间元素
for j in range(1, i):
row.append(prev_row[j - 1] + prev_row[j])
# 每行的最后一个元素总是1
row.append(1)
result.append(row)
return result
# 方法二:数学公式
def generateWithMath(self, numRows: int) -> List[List[int]]:
result = []
for i in range(numRows):
row = []
# 每行的第一个元素总是1
row.append(1)
# 使用组合数公式计算中间元素
for j in range(1, i + 1):
# C(i,j) = C(i,j-1) * (i-j+1) / j
row.append(row[j - 1] * (i - j + 1) // j)
result.append(row)
return result
C++ 实现
class Solution {
public:
// 方法一:动态规划
vector<vector<int>> generate(int numRows) {
vector<vector<int>> result;
// 处理第一行
result.push_back({1});
// 处理后续行
for (int i = 1; i < numRows; i++) {
vector<int> row;
const vector<int>& prevRow = result[i - 1];
// 每行的第一个元素总是1
row.push_back(1);
// 计算中间元素
for (int j = 1; j < i; j++) {
row.push_back(prevRow[j - 1] + prevRow[j]);
}
// 每行的最后一个元素总是1
row.push_back(1);
result.push_back(row);
}
return result;
}
// 方法二:数学公式
vector<vector<int>> generateWithMath(int numRows) {
vector<vector<int>> result;
for (int i = 0; i < numRows; i++) {
vector<int> row;
// 每行的第一个元素总是1
row.push_back(1);
// 使用组合数公式计算中间元素
for (int j = 1; j <= i; j++) {
// C(i,j) = C(i,j-1) * (i-j+1) / j
row.push_back((long long)row[j - 1] * (i - j + 1) / j);
}
result.push_back(row);
}
return result;
}
};
执行结果
C# 实现
- 执行用时:176 ms
- 内存消耗:36.2 MB
Python 实现
- 执行用时:32 ms
- 内存消耗:15.1 MB
C++ 实现
- 执行用时:0 ms
- 内存消耗:6.5 MB
性能对比
| 语言 | 执行用时 | 内存消耗 | 特点 |
|---|---|---|---|
| C# | 176 ms | 36.2 MB | 代码结构清晰,但性能较慢 |
| Python | 32 ms | 15.1 MB | 代码简洁,性能适中 |
| C++ | 0 ms | 6.5 MB | 执行速度最快,内存占用最少 |
代码亮点
- 🎯 利用杨辉三角的性质,通过动态规划高效构建
- 💡 方法二使用数学公式直接计算,避免了重复计算
- 🔍 正确处理了边界情况,如第一行和每行的首尾元素
- 🎨 代码结构清晰,变量命名规范,易于理解和维护
常见错误分析
- 🚫 忽略了杨辉三角每行首尾元素都是1的特性
- 🚫 在计算中间元素时,索引计算错误
- 🚫 在使用数学公式时,没有考虑整数溢出问题
- 🚫 没有正确初始化第一行,导致后续计算错误
解法对比
| 解法 | 时间复杂度 | 空间复杂度 | 优点 | 缺点 |
|---|---|---|---|---|
| 动态规划 | O(numRows²) | O(numRows²) | 实现简单,直观易懂 | 需要存储所有中间结果 |
| 数学公式 | O(numRows²) | O(numRows²) | 计算直接,无需依赖之前的行 | 需要注意整数溢出问题 |
相关题目
- LeetCode 119. 杨辉三角 II - 简单
- LeetCode 62. 不同路径 - 中等
- LeetCode 96. 不同的二叉搜索树 - 中等