LeetCode 119. 杨辉三角 II(原地更新优化详解 + Java/Python 实现)
题目链接
题目描述
给定一个非负索引 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. 杨辉三角 的进阶版本。不同之处在于:
- 只需要返回���
rowIndex行,不需要返回所有行。 - 进阶要求使用
O(rowIndex)的空间复杂度。
核心思想
杨辉三角的每一行都依赖于上一行,但我们可以通过 原地更新 来优化空间。关键技巧是 倒序更新,避免覆盖还未使用的数据。
方法一:双 List 法(空间 O(2k))
思路
这是最直观的解法,类似第 118 题的做法:
- 使用两个列表:
lastRow存储上一行,cur存储当前行。 - 从第 0 行开始,逐行生成,直到第
rowIndex行。 - 每行首尾都是 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),进阶最优)
思路
这是进阶解法,使用 一个列表 原地更新:
- 初始化
row = [1],表示第 0 行。 - 从第 1 行开始,逐行更新:
- 在行末尾添加 1(相当于每行的最后一个元素)。
- 倒序更新 中间元素:从
i-1到1,计算row[j] = row[j] + row[j-1]。
- 重复上述步骤,直到生成第
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
代码说明
-
初始化 :
row = [1],表示第 0 行。 -
外层循环 :
for (int i = 1; i <= rowIndex; i++)控制生成到第rowIndex行。 -
添加末尾 1 :
row.add(1);每行末尾都是 1。 -
倒序更新中间元素:
javafor (int j = i - 1; j > 0; j--) { row.set(j, row.get(j) + row.get(j - 1)); }j从i-1到1(倒序)。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 = 2row = [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 = 3j=1: row[1] = row[1] + row[0] = 2 + 1 = 3row = [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 = 4j=2: row[2] = row[2] + row[1] = 3 + 3 = 6j=1: row[1] = row[1] + row[0] = 3 + 1 = 4row = [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),符合要求。
总结
这道题是杨辉三角的进阶版本,核心要点:
- 只需返回一行:不需要保存所有行的数据。
- 原地更新:使用一个列表,通过倒序更新来避免数据覆盖。
- 倒序更新的原理 :确保
row[j-1]保存的是上一行的原始值。
关键点回顾:
- 初始化
row = [1] - 每行先添加末尾 1
- 倒序更新中间元素:
row[j] = row[j] + row[j-1] - 倒序更新的原因:避免覆盖未使用的数据
- 空间复杂度
O(rowIndex),时间复杂度O(rowIndex²)
这道题的倒序更新技巧非常经典,在很多需要原地更新数组的场景中都会用到。建议读者仔细理解倒序更新的原理,这对解决类似问题很有帮助。
如果觉得本文对你有帮助,欢迎点赞、收藏、关注,我会持续更新更多 LeetCode 题解和算法干货!