力扣--扁平化嵌套列表迭代器
生活随笔
收集整理的這篇文章主要介紹了
力扣--扁平化嵌套列表迭代器
小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
扁平化嵌套列表迭代器
文章目錄
- 扁平化嵌套列表迭代器
- 一、題目描述
- 二、分析
- 方法一:
- 代碼一:
- 方法二:
- 代碼二:
- C++代碼:
一、題目描述
/*** // This is the interface that allows for creating nested lists.* // You should not implement it, or speculate about its implementation* class NestedInteger {* public:* // Return true if this NestedInteger holds a single integer, rather than a nested list.* bool isInteger() const;** // Return the single integer that this NestedInteger holds, if it holds a single integer* // The result is undefined if this NestedInteger holds a nested list* int getInteger() const;** // Return the nested list that this NestedInteger holds, if it holds a nested list* // The result is undefined if this NestedInteger holds a single integer* const vector<NestedInteger> &getList() const;* };*/class NestedIterator { public:NestedIterator(vector<NestedInteger> &nestedList) {}int next() {}bool hasNext() {} };/*** Your NestedIterator object will be instantiated and called as such:* NestedIterator i(nestedList);* while (i.hasNext()) cout << i.next();*/二、分析
- 首先,現(xiàn)在有一種數(shù)據(jù)結(jié)構(gòu)NestedInteger,這個(gè)結(jié)構(gòu)中存的數(shù)據(jù)可能是一個(gè)Integer整數(shù),也可能是一個(gè)NestedInteger列表。
- 注意,這個(gè)列表里面裝著的是NestedInteger,也就是說這個(gè)列表中的每一個(gè)元素可能是個(gè)整數(shù),可能又是個(gè)列表,這樣無限遞歸嵌套下去……
- NestedInteger有如下 API:
- 我們的算法會(huì)被輸入一個(gè)NestedInteger列表,我們需要做的就是寫一個(gè)迭代器類,將這個(gè)帶有嵌套結(jié)構(gòu)NestedInteger的列表「拍平」:
方法一:
- 學(xué)過設(shè)計(jì)模式的朋友應(yīng)該知道,迭代器也是設(shè)計(jì)模式的一種,目的就是為調(diào)用者屏蔽底層數(shù)據(jù)結(jié)構(gòu)的細(xì)節(jié),簡單地通過hasNext和next方法有序地進(jìn)行遍歷。
- NestedInteger結(jié)構(gòu)實(shí)際上也是一種支持無限嵌套的結(jié)構(gòu),而且可以同時(shí)表示整數(shù)和列表兩種不同類型,我想 Notion 的核心數(shù)據(jù)結(jié)構(gòu) block 估計(jì)也是這樣的一種設(shè)計(jì)思路。
- 那么話說回來,對(duì)于這個(gè)算法問題,我們?cè)趺唇鉀Q呢?NestedInteger結(jié)構(gòu)可以無限嵌套,怎么把這個(gè)結(jié)構(gòu)「打平」,為迭代器的調(diào)用者屏蔽底層細(xì)節(jié),扁平化地輸出所有整數(shù)元素呢?
- 顯然,NestedInteger這個(gè)神奇的數(shù)據(jù)結(jié)構(gòu)是問題的關(guān)鍵,不過題目專門提醒我們:
我不應(yīng)該去嘗試實(shí)現(xiàn)NestedInteger這個(gè)結(jié)構(gòu),也不應(yīng)該去猜測它的實(shí)現(xiàn)?為什么?憑什么?是不是題目在誤導(dǎo)我?是不是我進(jìn)行推測之后,這道題就不攻自破了?
- 你看,我可不是什么好孩子,你不讓推測,我就偏偏要去推測!我反手就把NestedInteger這個(gè)結(jié)構(gòu)給實(shí)現(xiàn)出來:
- 嗯,其實(shí)這個(gè)實(shí)現(xiàn)也不難嘛,寫出來之后,發(fā)現(xiàn)這玩意兒竟然……
- 這玩意兒不就是棵 N 叉樹嗎?葉子節(jié)點(diǎn)是Integer類型,其val字段非空;其他節(jié)點(diǎn)都是List<NestedInteger>類型,其val字段為空,但是list字段非空,裝著孩子節(jié)點(diǎn)。
- 比如說輸入是[[1,1],2,[1,1]],其實(shí)就是如下樹狀結(jié)構(gòu):
- 好的,剛才題目說什么來著?把一個(gè)NestedInteger扁平化對(duì)吧?這不就等價(jià)于遍歷一棵 N叉樹的所有「葉子節(jié)點(diǎn)」嗎?我把所有葉子節(jié)點(diǎn)都拿出來,不就可以作為迭代器進(jìn)行遍歷了嗎?
- N 叉樹的遍歷怎么整?
- 這個(gè)框架可以遍歷所有節(jié)點(diǎn),而我們只對(duì)整數(shù)型的NestedInteger感興趣,也就是我們只想要「葉子節(jié)點(diǎn)」,所以traverse函數(shù)只要在到達(dá)葉子節(jié)點(diǎn)的時(shí)候把val加入結(jié)果列表即可
代碼一:
class NestedIterator implements Iterator<Integer> {private Iterator<Integer> it;public NestedIterator(List<NestedInteger> nestedList) {// 存放將 nestedList 打平的結(jié)果List<Integer> result = new LinkedList<>();for (NestedInteger node : nestedList) {// 以每個(gè)節(jié)點(diǎn)為根遍歷traverse(node, result);}// 得到 result 列表的迭代器this.it = result.iterator();}public Integer next() {return it.next();}public boolean hasNext() {return it.hasNext();} // 遍歷以 root 為根的多叉樹,將葉子節(jié)點(diǎn)的值加入 result 列表private void traverse(NestedInteger root, List<Integer> result) {if (root.isInteger()) {// 到達(dá)葉子節(jié)點(diǎn)result.add(root.getInteger());return;}// 遍歷框架for (NestedInteger child : root.getList()) {traverse(child, result);}} }這樣,我們就把原問題巧妙轉(zhuǎn)化成了一個(gè) N 叉樹的遍歷問題,并且得到了解法。
方法二:
- 以上解法雖然可以通過,但是在面試中,也許是有瑕疵的。
- 我們的解法中,一次性算出了所有葉子節(jié)點(diǎn)的值,全部裝到result列表,也就是內(nèi)存中,next和hasNext方法只是在對(duì)result列表做迭代。如果輸入的規(guī)模非常大,構(gòu)造函數(shù)中的計(jì)算就會(huì)很慢,而且很占用內(nèi)存。
- 一般的迭代器求值應(yīng)該是「惰性的」,也就是說,如果你要一個(gè)結(jié)果,我就算一個(gè)(或是一小部分)結(jié)果出來,而不是一次把所有結(jié)果都算出來。
- 如果想做到這一點(diǎn),使用遞歸函數(shù)進(jìn)行 DFS 遍歷肯定是不行的,而且我們其實(shí)只關(guān)心「葉子節(jié)點(diǎn)」,所以傳統(tǒng)的 BFS 算法也不行。實(shí)際的思路很簡單:
- 調(diào)用hasNext時(shí),如果nestedList的第一個(gè)元素是列表類型,則不斷展開這個(gè)元素,直到第一個(gè)元素是整數(shù)類型。
- 由于調(diào)用next方法之前一定會(huì)調(diào)用hasNext方法,這就可以保證每次調(diào)用next方法的時(shí)候第一個(gè)元素是整數(shù)型,直接返回并刪除第一個(gè)元素即可。
代碼二:
public class NestedIterator implements Iterator<Integer> {private LinkedList<NestedInteger> list;public NestedIterator(List<NestedInteger> nestedList) {// 不直接用 nestedList 的引用,是因?yàn)椴荒艽_定它的底層實(shí)現(xiàn)// 必須保證是 LinkedList,否則下面的 addFirst 會(huì)很低效list = new LinkedList<>(nestedList);}public Integer next() {// hasNext 方法保證了第一個(gè)元素一定是整數(shù)類型return list.remove(0).getInteger();}public boolean hasNext() {// 循環(huán)拆分列表元素,直到列表第一個(gè)元素是整數(shù)類型while (!list.isEmpty() && !list.get(0).isInteger()) {// 當(dāng)列表開頭第一個(gè)元素是列表類型時(shí),進(jìn)入循環(huán)List<NestedInteger> first = list.remove(0).getList();// 將第一個(gè)列表打平并按順序添加到開頭for (int i = first.size() - 1; i >= 0; i--) {list.addFirst(first.get(i));}}return !list.isEmpty();} }C++代碼:
- 時(shí)間均衡的棧
- 構(gòu)造時(shí)僅僅扒一層皮就 逆向 堆入棧中,在用戶調(diào)用 hasNext 時(shí)才做深入扒皮搜索。
- 這種做法比較時(shí)間均衡,如果用戶搞了一個(gè)很長的列表,然后就取前邊幾個(gè)元素就不用了,那這種實(shí)現(xiàn)要高效的多。
- 頭重腳輕的棧
- 構(gòu)造時(shí)扒皮抽骨到單個(gè)數(shù)字再 push 到棧里。這樣預(yù)處理很慢,但是調(diào)用時(shí)很快。有點(diǎn)頭重腳輕。
- 頭重腳輕的 vector
- 不用棧,用 vector 來做也可以。只是存儲(chǔ)的時(shí)候不再是從尾到頭而是從頭到尾啦。
總結(jié)
以上是生活随笔為你收集整理的力扣--扁平化嵌套列表迭代器的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 美团--最小唯一前缀
- 下一篇: 美团--订单分配