4.10 扁平化嵌套序列
4.10.1 问题描述
给你一个嵌套的整数列表 nestedList 。每个元素要么是一个整数,要么是一个列表;该列表的元素也可能是整数或者是其他列表。请你实现一个迭代器将其扁平化,使之能够遍历这个列表中的所有整数。
实现扁平迭代器类 NestedIterator :
NestedIterator(List
int next() 返回嵌套列表的下一个整数。
boolean hasNext() 如果仍然存在待迭代的整数,返回 true ;否则,返回 false 。
你的代码将会用下述伪代码检测:
来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/flatten-nested-list-iterator
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
有一种数据结构叫做
NestedInteger,这个结构中存的数据可能是一个Integer整数,也有可能是是一个NestedInteger的列表,注意,这个列表里面装的是NestdInteger,也就是说这个列表中的每一个元素可能是个整数,也可能是个列表,这样无限递归嵌套下去我们到的算法将被输入一个
NestedInteger列表,需要做的就是写一个迭代器类,将这个带有嵌套结构的NestedInteger给拍平
public class NestedIterator implements Iterator<Integer> {
public NestedIterator(List<NestedInteger> nestedList) {
}
@Override
public Integer next() {
}
@Override
public boolean hasNext() {
}
}
- 我们写的这个类将被这样调用,先调用
hasNext()方法,然后调用next方法
while iterator.hasNext()
append iterator.next() to the end of res
return res
- 样例分析:假设输入
[[1,1],2,[1,1]],其中存在三个NestedInteger,两个列表类型的NestedInteger和一个整数类型的NestedInteger,算法返回打平的列表[1,1,2,1,1] - 假设输入
[1,[4,[6]]],算法返回打平的列表[1,4,6] - 迭代器模式是设计模式的一种,它为调用者屏蔽底层数据结构的细节,简单通过next()和hasNext()方法有序地进行遍历
- 回想这个算法问题,
NestedInteger结构实际上是一种支持无限嵌套的数据结构,而且可以同时表示整数和列表两种不同的类型
4.10.2 解题思路
public class NestedInteger {
private Integer val;
private List<NestedInteger> list;
public NestedInteger(Integer val){
this.val = val;
this.list = null;
}
public NestedInteger(List<NestedInteger> list){
this.list = list;
}
//如果其中存储的是一个整数,则返回true,否则返回false
public boolean isInteger(){
return this.val!=null;
}
//如果存储的是整数,则返回这个整数,否则返回null
public Integer getVal(){
return this.val;
}
//如果存储的是列表,则返回这个列表
public List<NestedInteger> getList() {
return list;
}
}
- 由题意猜测其内部数据结构应该是类似这样的结构
- 对比N叉树的定义:其叶子节点是
Integer类型,其val字段非空,其他节点都是List<leetocde_1.TreeNode>类型,其val字段为空,但是list字段非空,存着孩子节点
class leetocde_1.TreeNode{
int val;
List<leetocde_1.TreeNode> children
}
- 那么既然是N叉树的序列化问题,那么就可以转化为N叉树的遍历问题,将所有的叶子节点都给拿出来,就可以作为迭代器进行遍历了
void traverse(leetocde_1.TreeNode root){
//根节点该怎么做:把自己的所有子节点都给拿出来,遍历,剩下的抛给递归
for(leetocde_1.TreeNode node:root.children){
traverse(node);
}
}
- 这个框架可以遍历所有的节点,而我们只对整数类型的
NestedInteger感兴趣,也就是只想要叶子节点,所以traverse函数只要在到达叶子节点的时候把val加入结果列表即可
private Iterator<Integer> it;
public NestedIterator(List<NestedInteger> nestedList) {
//存放将nestedList打平的结果
List<Integer> result = new LinkedList<>();
for(NestedInteger node:nestedList){
this.traverse(node,result);
}
//得到result列表的迭代器
this.it = result.iterator();
}
private void traverse(NestedInteger root,List<Integer> result){
if(root.isInteger()){
result.add(root.getInteger());
return;
}
for(NestedInteger node:root.getList()){
this.traverse(node,result);
}
}
@Override
public Integer next() {
return it.next();
}
@Override
public boolean hasNext() {
return it.hasNext();
}
4.10.3 进阶思路
- 一般的迭代器求值应该是惰性的,也就是说,如果你只要一个结果,那么我就算一个(或者是一小部分)结果出来,而不是一次把所有结果都算出来,而在上述解法中,一次性算出了所有叶子节点的值,全部装到了
result列表,也就是内存中,next和hasNext只是在对result做迭代,如果输入的规模非常大,构造函数中的计算就会很慢,而且很占用内存 - 解决思路:调用hasNext()时,如果
nestedList的第一个元素是列表类型,则不断展开这个元素,直到第一个元素是整数类型,由于调用next()方法之前肯定会调用hasNext,这样就可以保证每次调用next方法的时候列表的第一个元素是整数类型,直接返回并删除第一个元素即可
private LinkedList<NestedInteger> list;
public NestedIterator(List<NestedInteger> nestedList) {
//不直接用nestedList的引用,是因为不能确定它的底层实现
//必须保证是LinkedList,否则addFirst会很低效
list = new LinkedList<>(nestedList);
}
@Override
public Integer next() {
//hasNext()方法保证了第一个元素一定是整数类型
return list.remove(0).getInteger();
}
@Override
public boolean hasNext() {
//循环拆分列表,直到第一个元素是整数类型
while(!list.isEmpty() && !list.get(0).isInteger()){
//当列表第一个元素是列表类型时,进入循环
List<NestedInteger> first = list.remove(0).getList();
//将第一个列表打平并按照顺序添加到开头
for(int i = first.size()-1;i>=0;i--){
list.addFirst(first.get(i));
}
}
return !list.isEmpty();
}