LeetCode 341. 扁平化嵌套列表迭代器
题目描述


题意分析
输入是一个嵌套列表,每一项要么是整数,要么是仍可继续嵌套的列表。需要按原始从左到右的顺序逐个返回全部整数,空列表不产生任何输出,列表本身也不是返回值。
迭代器需要在多次方法调用之间保存进度。
hasNext判断是否还存在下一个整数,next取得这个整数。题目给出的测试流程是先调用hasNext,返回真后才调用next;本实现让前者准备好下一个值,后者只负责消费它。
解法:栈模拟
核心思路
[!blue]
用显式栈保存尚未消费的嵌套项,栈顶表示接下来最先处理的一项。构造时将顶层列表从后向前压栈,抵消栈后进先出的顺序,使原来的第一项位于栈顶。
若栈顶已经是整数,它就是接下来应返回的值;若栈顶是列表,就先弹出这个列表,再把它的子项逆序压入。子项会位于原来其他待处理项之上,因此整棵当前子列表会先被处理,之后才轮到外层的下一个兄弟项,保持嵌套结构的原始顺序。
准备下一个值不能只展开一次。列表里面可能仍然是列表,也可能为空,因此
hasNext持续展开栈顶,直到发现整数或栈清空。空列表被弹出后不压入任何项,继续检查后面的工作;所以栈非空并不等于一定还有整数。发现栈顶为整数时,
hasNext返回真但不弹出它。这样连续多次询问可用性仍会看到同一个整数,不会跳过数据。只有调用next时才弹出这个已准备的整数,让迭代进度前进一步。每个嵌套项至多入栈、出栈一次,不提前把所有整数复制到单独数组。栈保存的是尚未处理的工作,并不表示其中所有列表都已展开;处理会随着调用逐步发生。
解题步骤
- 构造时将顶层各项逆序压栈。
hasNext中反复检查栈顶:整数则返回真,列表则弹出并逆序压入它的子项。- 若展开后栈清空,返回假,表示后面已经没有整数。
- 按题目调用协议,在
hasNext返回真后,next弹出栈顶并返回其整数值。
代码实现
public class NestedIterator implements Iterator<Integer> {
private final Deque<NestedInteger> stack = new ArrayDeque<>();
public NestedIterator(List<NestedInteger> nestedList) {
// 逆序入栈,让原列表最前项先被处理
for (int i = nestedList.size() - 1; i >= 0; i--) {
stack.push(nestedList.get(i));
}
}
@Override
public Integer next() {
return stack.pop().getInteger();
}
@Override
public boolean hasNext() {
// hasNext 负责把栈顶列表展开到栈顶为整数。
while (!stack.isEmpty()) {
NestedInteger top = stack.peek();
// 只检查不消费,重复询问仍保留同一个整数
if (top.isInteger()) {
return true;
}
// 先移除待展开列表,再将子项逆序压入
stack.pop();
List<NestedInteger> list = top.getList();
for (int i = list.size() - 1; i >= 0; i--) {
stack.push(list.get(i));
}
}
return false;
}
}
type NestedIterator struct {
// 为了保持原始顺序,向栈中放入列表元素时必须从后往前压入。
stack []*NestedInteger
}
func Constructor(nestedList []*NestedInteger) *NestedIterator {
stack := make([]*NestedInteger, 0, len(nestedList))
// 逆序入栈,让原列表最前项先被处理
for i := len(nestedList) - 1; i >= 0; i-- {
stack = append(stack, nestedList[i])
}
return &NestedIterator{stack: stack}
}
func (it *NestedIterator) Next() int {
n := it.stack[len(it.stack)-1]
it.stack = it.stack[:len(it.stack)-1]
return n.GetInteger()
}
func (it *NestedIterator) HasNext() bool {
for len(it.stack) > 0 {
top := it.stack[len(it.stack)-1]
// 只检查不消费,重复询问仍保留同一个整数
if top.IsInteger() {
return true
}
// 先移除待展开列表,再将子项逆序压入
it.stack = it.stack[:len(it.stack)-1]
list := top.GetList()
for i := len(list) - 1; i >= 0; i-- {
it.stack = append(it.stack, list[i])
}
}
return false
}
复杂度分析
设顶层项数为
k,所有层的整数项与列表项合计为N,可用性检查调用次数为Q。
- 时间复杂度:构造为 $O(k)$;完成全部遍历及
Q次hasNext共 $O(N + Q)$。每个嵌套项只展开或消费一次,但一次hasNext可能连续经过大量列表,最坏为 $O(N)$;已经准备好的next为 $O(1)$。- 空间复杂度:$O(N)$,显式栈可能同时保存大量尚未处理的兄弟项,不能只按嵌套深度估计。
关键点总结
[!green]
- 栈顶表示下一项工作,子列表逆序入栈保证其内部与外部的读取顺序。
- 可用性检查允许展开结构,但不能消费已经准备好的整数。
- 复杂度按所有嵌套项计数,空列表虽然不输出整数,仍然需要处理。
易错点总结
[!yellow]
- 正序压栈会让列表最后一项先被读取,颠倒原始顺序。
- 只展开一层就返回真,可能把更深列表或空列表误当作可用整数。
- 展开子项前不弹出原列表,会在之后反复处理同一列表。
hasNext找到整数时就把它弹出,会让重复检查跳过数据。- 仅检查栈是否非空,无法判断剩余内容是否全部为空列表。
- 把每次
hasNext都写成常数时间,忽略了它可能一次展开多层或多项结构。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 251. 展开二维向量 | 中等 | 二维向量只需行列游标,本题嵌套深度不固定,需要栈或递归保存展开状态。 |
| 385. 迷你语法分析器 | 中等 | 原题把文本解析成嵌套整数结构,本题遍历已经构造好的结构并延迟展开。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!