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