LeetCode 119. 杨辉三角 II(原地更新优化详解 + Java Python 实现)

LeetCode 119. 杨辉三角 II(原地更新优化详解 + Java/Python 实现)

题目链接

119. 杨辉三角 II - 力扣(LeetCode)


题目描述

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

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


示例

示例 1

text 复制代码
输入: rowIndex = 3
输出: [1,3,3,1]

示例 2

text 复制代码
输入: rowIndex = 0
输出: [1]

示例 3

text 复制代码
输入: rowIndex = 1
输出: [1,1]

提示

  • 0 <= rowIndex <= 33

进阶要求

你可以优化你的算法到 O(rowIndex) 空间复杂度吗?


解题思路

这道题是 LeetCode 118. 杨辉三角 的进阶版本。不同之处在于:

  1. 只需要返回��� rowIndex 行,不需要返回所有行。
  2. 进阶要求使用 O(rowIndex) 的空间复杂度。

核心思想

杨辉三角的每一行都依赖于上一行,但我们可以通过 原地更新 来优化空间。关键技巧是 倒序更新,避免覆盖还未使用的数据。


方法一:双 List 法(空间 O(2k))

思路

这是最直观的解法,类似第 118 题的做法:

  1. 使用两个列表:lastRow 存储上一行,cur 存储当前行。
  2. 从第 0 行开始,逐行生成,直到第 rowIndex 行。
  3. 每行首尾都是 1,中间元素由上一行相邻元素相加得到。

Java 代码

java 复制代码
import java.util.ArrayList;
import java.util.List;

class Solution {
    public List<Integer> getRow(int rowIndex) {
        List<Integer> lastRow = new ArrayList<>();
        lastRow.add(1);
        for (int i = 1; i <= rowIndex; i++){
            List<Integer> cur = new ArrayList<>();
            cur.add(1);
            for (int j = 1; j < i; j++){
                cur.add(lastRow.get(j-1) + lastRow.get(j));
            }
            cur.add(1);
            lastRow = cur;
        }
        return lastRow;
    }
}

Python 代码

python 复制代码
from typing import List

class Solution:
    def getRow(self, rowIndex: int) -> List[int]:
        last_row = [1]
        for i in range(1, rowIndex + 1):
            cur = [1]
            for j in range(1, i):
                cur.append(last_row[j-1] + last_row[j])
            cur.append(1)
            last_row = cur
        return last_row

复杂度分析

  • 时间复杂度O(rowIndex²),需要生成每一行的所有元素。
  • 空间复杂度O(rowIndex),需要存储上一行和当前行。

缺点

虽然空间复杂度是 O(rowIndex),但实际上是 O(2*rowIndex),因为需要同时保存上一行和当前行。这不符合进阶要求的 O(rowIndex) 最优空间。


方法二:原地单 List 法(空间 O(k),进阶最优)

思路

这是进阶解法,使用 一个列表 原地更新:

  1. 初始化 row = [1],表示第 0 行。
  2. 从第 1 行开始,逐行更新:
    • 在行末尾添加 1(相当于每行的最后一个元素)。
    • 倒序更新 中间元素:从 i-11,计算 row[j] = row[j] + row[j-1]
  3. 重复上述步骤,直到生成第 rowIndex 行。

为什么必须倒序更新?

这是本解法的关键!假设我们要生成第 3 行 [1,3,3,1],当前 row = [1,2,1](第 2 行)。

如果正序更新(从前往后):

  • row[1] = row[0] + row[1] = 1 + 2 = 3,此时 row = [1,3,1]
  • row[2] = row[1] + row[2] = 3 + 1 = 4,此时 row = [1,3,4]
  • 结果错误! 因为 row[1] 已经被更新成 3,导致 row[2] 计算时使用了错误的值。

如果倒序更新(从后往前):

  • 先添加末尾 1:row = [1,2,1,1]
  • row[2] = row[1] + row[2] = 2 + 1 = 3,此时 row = [1,2,3,1]
  • row[1] = row[0] + row[1] = 1 + 2 = 3,此时 row = [1,3,3,1]
  • 结果正确! 因为从后往前更新时,row[j-1] 还未被更新,保存的是上一行的原始值。

Java 代码

java 复制代码
import java.util.ArrayList;
import java.util.List;

class Solution {
    public List<Integer> getRow(int rowIndex) {
        List<Integer> row = new ArrayList<>();
        row.add(1);
        for (int i = 1; i <= rowIndex; i++) {
            row.add(1);
            // 倒序更新,防止上一行原始数据被覆盖
            for (int j = i - 1; j > 0; j--) {
                row.set(j, row.get(j) + row.get(j - 1));
            }
        }
        return row;
    }
}

Python 代码

python 复制代码
from typing import List

class Solution:
    def getRow(self, rowIndex: int) -> List[int]:
        row = [1]
        for i in range(1, rowIndex + 1):
            row.append(1)
            # 倒序遍历
            for j in range(i - 1, 0, -1):
                row[j] = row[j] + row[j - 1]
        return row

代码说明

  1. 初始化row = [1],表示第 0 行。

  2. 外层循环for (int i = 1; i <= rowIndex; i++) 控制生成到第 rowIndex 行。

  3. 添加末尾 1row.add(1); 每行末尾都是 1。

  4. 倒序更新中间元素

    java 复制代码
    for (int j = i - 1; j > 0; j--) {
        row.set(j, row.get(j) + row.get(j - 1));
    }
    • ji-11(倒序)。
    • row[j] = row[j] + row[j-1],即当前元素更新为原值加上前一个元素。
    • 由于是倒序,row[j-1] 还未被更新,保存的是上一行的值。

复杂度分析

  • 时间复杂度O(rowIndex²),需要生成每一行的所有元素。
  • 空间复杂度O(rowIndex),只使用一个列表,符合进阶要求。

举例说明

rowIndex = 4 为例,详细展示原地更新过程:

初始: row = [1]

第 1 行(i=1):

  • 添加末尾 1:row = [1,1]
  • 倒序更新:j 从 0 到 1(没有)
  • 结果:row = [1,1]

第 2 行(i=2):

  • 添加末尾 1:row = [1,1,1]
  • 倒序更新:j = 1
  • row[1] = row[1] + row[0] = 1 + 1 = 2
  • row = [1,2,1]

第 3 行(i=3):

  • 添加末尾 1:row = [1,2,1,1]
  • 倒序更新:j = 2, 1
  • j=2: row[2] = row[2] + row[1] = 1 + 2 = 3
  • j=1: row[1] = row[1] + row[0] = 2 + 1 = 3
  • row = [1,3,3,1]

第 4 行(i=4):

  • 添加末尾 1:row = [1,3,3,1,1]
  • 倒序更新:j = 3, 2, 1
  • j=3: row[3] = row[3] + row[2] = 1 + 3 = 4
  • j=2: row[2] = row[2] + row[1] = 3 + 3 = 6
  • j=1: row[1] = row[1] + row[0] = 3 + 1 = 4
  • row = [1,4,6,4,1]

最终返回 [1,4,6,4,1]


两种方法对比

方法 时间复杂度 空间复杂度 优缺点
双 List O(rowIndex²) O(2k) 思路简单,但空间较大
原地单 List O(rowIndex²) O(k) 空间最优,但需要理解倒序更新

常见问题解答

Q1:为什么倒序更新可以避免数据覆盖?

因为倒序更新时,row[j] 的更新依赖于 row[j-1],而 row[j-1] 是索引较小的元素��从后往前遍历时,索引较小的元素还未被更新,保存的是上一行的原始值。如果正序遍历,索引较小的元素先被更新,导致后续计算使用错误的值。

Q2:这道题和第 118 题有什么区别?

第 118 题要求返回整个杨辉三角,而这道题只要求返回第 rowIndex 行。因此,这道题可以通过原地更新来优化空间,不需要保存所有行的数据。

Q3:进阶要求中的 O(rowIndex) 空间是否包含返回结果占用的空间?

通常来说,空间复杂度分析时不考虑返回结果本身占用的空间,只计算额外的辅助空间。但即使包含返回结果,原地单 List 法的空间复杂度也是 O(rowIndex),符合要求。


总结

这道题是杨辉三角的进阶版本,核心要点:

  1. 只需返回一行:不需要保存所有行的数据。
  2. 原地更新:使用一个列表,通过倒序更新来避免数据覆盖。
  3. 倒序更新的原理 :确保 row[j-1] 保存的是上一行的原始值。

关键点回顾:

  • 初始化 row = [1]
  • 每行先添加末尾 1
  • 倒序更新中间元素:row[j] = row[j] + row[j-1]
  • 倒序更新的原因:避免覆盖未使用的数据
  • 空间复杂度 O(rowIndex),时间复杂度 O(rowIndex²)

这道题的倒序更新技巧非常经典,在很多需要原地更新数组的场景中都会用到。建议读者仔细理解倒序更新的原理,这对解决类似问题很有帮助。

如果觉得本文对你有帮助,欢迎点赞、收藏、关注,我会持续更新更多 LeetCode 题解和算法干货!

相关推荐
jay神3 小时前
深度学习的优化器应该怎么选?
人工智能·python·深度学习·毕业设计·课程设计
程序员黑豆7 小时前
Java包装类:基本类型与对象的桥梁
java·前端·ai编程
mldong10 小时前
"applicant" 契约:退回发起人的闭环设计
java·架构
月华路11 小时前
G1 垃圾回收:脏卡队列、记忆集与并发精化线程机制
java·开发语言
XLYcmy11 小时前
京东 算法实习一面 下+手撕
c++·python·llm·概率论·数据处理·训练·codebert
虎头金猫11 小时前
如何在群晖NAS上通过Docker部署CloudSaver?群晖部署CloudSaver教程|聚合资源搜索并实现远程访问
运维·服务器·网络·python·docker·容器·pandas
从零开始学习人工智能11 小时前
WSL2部署CUDA12+cuDNN9踩坑实录:ONNX Runtime GPU推理环境搭建全流程
python
gugucoding11 小时前
47. 【Java】Java内存模型(JMM)与可见性
java·开发语言