LeetCode第119题_杨辉三角II

LeetCode 第119题:杨辉三角 II

题目描述

给定一个非负索引 rowIndex,返回「杨辉三角」的第 rowIndex 行。

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

难度

简单

题目链接

点击在LeetCode中查看题目

示例

示例 1:

输入:rowIndex = 3
输出:[1,3,3,1]

示例 2:

输入:rowIndex = 0
输出:[1]

示例 3:

输入:rowIndex = 1
输出:[1,1]

提示

  • 0 <= rowIndex <= 33

解题思路

方法一:逐行计算

与第118题类似,我们可以逐行计算杨辉三角,直到计算到第rowIndex行。不同的是,这里只需要返回最后一行,而不是整个三角形。

关键点:

  • 每行的第一个和最后一个元素都是1
  • 对于其他位置的元素,其值等于上一行的相邻两个元素之和

具体步骤:

  1. 初始化第一行为[1]
  2. 对于后续的每一行i(从1到rowIndex):
    a. 创建一个新的列表row,长度为i+1
    b. 设置row[0]和row[i]为1
    c. 对于row中的其他位置j(从1到i-1),设置row[j] = prevRow[j-1] + prevRow[j]
    d. 将prevRow更新为row
  3. 返回最后计算的行

时间复杂度:O(rowIndex²),需要计算rowIndex+1行,每行最多有rowIndex+1个元素
空间复杂度:O(rowIndex),只需要存储最后一行

方法二:空间优化的动态规划

我们可以只使用一个数组来计算杨辉三角的每一行,通过从后向前更新数组来避免覆盖还未使用的值。

关键点:

  • 使用一个长度为rowIndex+1的数组
  • 从后向前更新数组的每个元素
  • 每次更新时,当前位置的新值等于当前值加上前一个位置的值

具体步骤:

  1. 初始化一个长度为rowIndex+1的数组row,所有元素设为0,第一个元素设为1
  2. 对于每一行i(从1到rowIndex):
    a. 从后向前遍历j(从i到1):
    i. 更新row[j] = row[j] + row[j-1]
  3. 返回最终的row数组

时间复杂度:O(rowIndex²),需要计算rowIndex+1行,每行最多有rowIndex+1个元素
空间复杂度:O(rowIndex),只需要一个长度为rowIndex+1的数组

方法三:数学公式

杨辉三角的每一行实际上是二项式展开的系数,可以使用组合数公式直接计算。

关键点:

  • 第n行第k个元素的值为组合数C(n,k),即C(n,k) = n! / (k! * (n-k)!)
  • 可以利用递推公式C(n,k) = C(n,k-1) * (n-k+1) / k来优化计算

具体步骤:

  1. 创建一个长度为rowIndex+1的数组row
  2. 设置row[0]为1
  3. 对于row中的其他位置j(从1到rowIndex),使用递推公式计算row[j] = row[j-1] * (rowIndex-j+1) / j
  4. 返回row数组

时间复杂度:O(rowIndex),只需要计算rowIndex+1个元素
空间复杂度:O(rowIndex),需要一个长度为rowIndex+1的数组

图解思路

方法二:空间优化的动态规划过程

以rowIndex = 4为例,计算第4行[1,4,6,4,1]的过程:

迭代 row数组 更新过程
初始化 [1,0,0,0,0] 初始化数组,第一个元素为1
i=1, j=1 [1,1,0,0,0] row[1] = row[1] + row[0] = 0 + 1 = 1
i=2, j=2 [1,1,1,0,0] row[2] = row[2] + row[1] = 0 + 1 = 1
i=2, j=1 [1,2,1,0,0] row[1] = row[1] + row[0] = 1 + 1 = 2
i=3, j=3 [1,2,1,1,0] row[3] = row[3] + row[2] = 0 + 1 = 1
i=3, j=2 [1,2,3,1,0] row[2] = row[2] + row[1] = 1 + 2 = 3
i=3, j=1 [1,3,3,1,0] row[1] = row[1] + row[0] = 2 + 1 = 3
i=4, j=4 [1,3,3,1,1] row[4] = row[4] + row[3] = 0 + 1 = 1
i=4, j=3 [1,3,3,4,1] row[3] = row[3] + row[2] = 1 + 3 = 4
i=4, j=2 [1,3,6,4,1] row[2] = row[2] + row[1] = 3 + 3 = 6
i=4, j=1 [1,4,6,4,1] row[1] = row[1] + row[0] = 3 + 1 = 4

最终结果:[1,4,6,4,1]

方法三:数学公式计算过程

以rowIndex = 4为例,计算第4行[1,4,6,4,1]的过程:

j 计算公式 结果
0 - 1
1 row[0] * (4-1+1) / 1 = 1 * 4 / 1 4
2 row[1] * (4-2+1) / 2 = 4 * 3 / 2 6
3 row[2] * (4-3+1) / 3 = 6 * 2 / 3 4
4 row[3] * (4-4+1) / 4 = 4 * 1 / 4 1

最终结果:[1,4,6,4,1]

代码实现

C# 实现

public class Solution {
    // 方法一:逐行计算
    public IList<int> GetRow(int rowIndex) {
        IList<int> row = new List<int> { 1 };
        
        for (int i = 1; i <= rowIndex; i++) {
            List<int> newRow = new List<int>();
            newRow.Add(1);
            
            for (int j = 1; j < i; j++) {
                newRow.Add(row[j - 1] + row[j]);
            }
            
            newRow.Add(1);
            row = newRow;
        }
        
        return row;
    }
    
    // 方法二:空间优化的动态规划
    public IList<int> GetRowOptimized(int rowIndex) {
        int[] row = new int[rowIndex + 1];
        row[0] = 1;
        
        for (int i = 1; i <= rowIndex; i++) {
            for (int j = i; j > 0; j--) {
                row[j] += row[j - 1];
            }
        }
        
        return row.ToList();
    }
    
    // 方法三:数学公式
    public IList<int> GetRowMath(int rowIndex) {
        List<int> row = new List<int>();
        long val = 1;
        row.Add(1);
        
        for (int j = 1; j <= rowIndex; j++) {
            val = val * (rowIndex - j + 1) / j;
            row.Add((int)val);
        }
        
        return row;
    }
}

Python 实现

class Solution:
    # 方法一:逐行计算
    def getRow(self, rowIndex: int) -> List[int]:
        row = [1]
        
        for i in range(1, rowIndex + 1):
            new_row = [1]
            
            for j in range(1, i):
                new_row.append(row[j - 1] + row[j])
            
            new_row.append(1)
            row = new_row
        
        return row
    
    # 方法二:空间优化的动态规划
    def getRowOptimized(self, rowIndex: int) -> List[int]:
        row = [0] * (rowIndex + 1)
        row[0] = 1
        
        for i in range(1, rowIndex + 1):
            for j in range(i, 0, -1):
                row[j] += row[j - 1]
        
        return row
    
    # 方法三:数学公式
    def getRowMath(self, rowIndex: int) -> List[int]:
        row = [1]
        val = 1
        
        for j in range(1, rowIndex + 1):
            val = val * (rowIndex - j + 1) // j
            row.append(val)
        
        return row

C++ 实现

class Solution {
public:
    // 方法一:逐行计算
    vector<int> getRow(int rowIndex) {
        vector<int> row = {1};
        
        for (int i = 1; i <= rowIndex; i++) {
            vector<int> newRow = {1};
            
            for (int j = 1; j < i; j++) {
                newRow.push_back(row[j - 1] + row[j]);
            }
            
            newRow.push_back(1);
            row = newRow;
        }
        
        return row;
    }
    
    // 方法二:空间优化的动态规划
    vector<int> getRowOptimized(int rowIndex) {
        vector<int> row(rowIndex + 1, 0);
        row[0] = 1;
        
        for (int i = 1; i <= rowIndex; i++) {
            for (int j = i; j > 0; j--) {
                row[j] += row[j - 1];
            }
        }
        
        return row;
    }
    
    // 方法三:数学公式
    vector<int> getRowMath(int rowIndex) {
        vector<int> row = {1};
        long long val = 1;
        
        for (int j = 1; j <= rowIndex; j++) {
            val = val * (rowIndex - j + 1) / j;
            row.push_back(val);
        }
        
        return row;
    }
};

执行结果

C# 实现

  • 执行用时:92 ms
  • 内存消耗:35.8 MB

Python 实现

  • 执行用时:28 ms
  • 内存消耗:15.0 MB

C++ 实现

  • 执行用时:0 ms
  • 内存消耗:6.4 MB

性能对比

语言 执行用时 内存消耗 特点
C# 92 ms 35.8 MB 代码结构清晰,但性能较慢
Python 28 ms 15.0 MB 代码简洁,性能适中
C++ 0 ms 6.4 MB 执行速度最快,内存占用最少

代码亮点

  1. 方法二通过从后向前更新数组,巧妙地实现了O(rowIndex)的空间复杂度
  2. 方法三利用数学公式直接计算,避免了重复计算,效率最高
  3. 在数学公式方法中,使用了long类型避免整数溢出问题
  4. 三种方法各有优势,可以根据具体需求选择合适的实现

常见错误分析

  1. 没有从后向前更新数组,导致覆盖了还未使用的值
  2. 在计算组合数时没有考虑整数溢出问题
  3. 错误地理解了rowIndex,没有考虑到索引从0开始
  4. 在方法一中重复创建新数组,导致不必要的内存消耗

解法对比

解法 时间复杂度 空间复杂度 优点 缺点
逐行计算 O(rowIndex²) O(rowIndex) 实现简单,直观易懂 需要创建多个临时数组
空间优化的动态规划 O(rowIndex²) O(rowIndex) 只需要一个数组,空间效率高 需要注意更新顺序
数学公式 O(rowIndex) O(rowIndex) 计算最直接,效率最高 需要注意整数溢出问题

相关题目

  • LeetCode 118. 杨辉三角 - 简单
  • LeetCode 62. 不同路径 - 中等
  • LeetCode 96. 不同的二叉搜索树 - 中等

你可能感兴趣的:(算法,leetcode,算法,职场和发展,c++,数据结构,python,c#)