編程題-從前序與中序遍歷序列構造二叉樹(中等-重點)

題目:

給定兩個整數數組?preorderinorder?,其中?preorder 是二叉樹的先序遍歷inorder?是同一棵樹的中序遍歷,請構造二叉樹并返回其根節點。

提示:

  • preorder?和?inorder?均 無重復 元素

解法一(遞歸):

在「遞歸」地遍歷某個子樹的過程中,我們也是將這顆子樹看成一顆全新的樹。在中序遍歷中定位到根節點,就可以分別知道左子樹和右子樹中的節點數目。由于同一顆子樹的前序遍歷和中序遍歷的長度顯然是相同的,因此我們可以對應到前序遍歷的結果中。

在中序遍歷中對根節點進行定位時,一種簡單的方法是直接掃描整個中序遍歷的結果并找出根節點,但這樣做的時間復雜度較高。我們可以考慮使用哈希表來幫助我們快速地定位根節點。對于哈希表映射中的每個鍵值對,鍵表示一個元素(節點的值),值表示其在中序遍歷中的出現位置。在構造二叉樹的過程之前,我們可以對中序遍歷的列表進行一遍掃描,就可以構造這個哈希映射。在此后構造二叉樹的過程中,我們就只需要O(1)的時間對根節點進行定位了。如下為實現代碼:

class Solution {
private:unordered_map<int, int> index;public:TreeNode* myBuildTree(const vector<int>& preorder, const vector<int>& inorder, int preorder_left, int preorder_right, int inorder_left, int inorder_right){if(preorder_left>preorder_right){return nullptr;}// 前序遍歷中的第一個節點就是根節點int preorder_root = preorder_left;// 在中序遍歷中定位根節點int inorder_root = index[preorder[preorder_root]];// 先把根節點建立起來TreeNode* root = new TreeNode(preorder[preorder_root]);// 得到左子樹中的節點數目int size_left_subtree = inorder_root - inorder_left;// 遞歸地構造左子樹,并連接到根節點// 先序遍歷中【從左邊界+1開始的size_left_subtree】個元素就對應了中序遍歷中【從左邊界開始到根節點定位-1】的元素root->left = myBuildTree(preorder, inorder, preorder_left+1, preorder_left + size_left_subtree, inorder_left, inorder_root-1);// 遞歸地構造右子樹,并連接到根節點root->right = myBuildTree(preorder,inorder,preorder_left+1+size_left_subtree, preorder_right, inorder_root+1,inorder_right);return root;}TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {int n = preorder.size();// 構造哈希映射,幫助我們快速定位根節點for (int i = 0; i < n; ++i) {index[inorder[i]] = i;}return myBuildTree(preorder, inorder, 0, n - 1, 0, n - 1);}
};

?時間復雜度:O(n),其中n是樹中的節點個數。空間復雜度:O(n),除去返回的答案需要的O(n)空間之外,我們還需要使用O(n)的空間存儲哈希映射,以及O(h)(其中h是樹的高度)的空間表示遞歸時棧空間。這里h<n,所以總空間復雜度為O(n)。

解法二(迭代):

?對于前序遍歷中的任意兩個連續節點u和v,根據前序遍歷的流程,我們可以直到u和v只有兩種可能的關系:

1、v是u的左兒子。這是因此在遍歷之后,下一個遍歷的節點就是u的左兒子,即v;

2、u沒有左兒子,并且v是u的某個祖先節點(或者u本身)的右兒子,如果u沒有左兒子,那么下一個遍歷的節點就是u的右兒子。如果u沒有右兒子,我們就會向上回溯,直到遇到第一個有右兒子(且u不在它的右兒子的子樹中)的節點ua,那么v就是ua的右兒子。

我們用一個棧stack來維護「當前節點的所有還沒有考慮過右兒子的祖先節點」,棧頂就是當前節點。也就是說,只有在棧中的節點才可能連接一個新的右兒子。同時,我們用一個指針index指向中序遍歷的某個位置,初始值為0。index對應的節點是「當前節點不斷往左走達到的最終節點」,這也是符合中序遍歷的,它的作用在下面的過程中會有所體現。

首先我們將根節點3入棧,再初始化index所指向的節點為4,隨后對于前序遍歷中的每個節點,我們依次判斷它是棧頂節點的左兒子,還是棧中某個節點的右兒子。

我們遍歷9。9一定是棧頂節點3的左兒子。我們使用反證法,假設9是3的右兒子,那么3沒有左兒子,index應該恰好指向3,但實際上為4,因此產生了矛盾。所以我們將9作為3的左兒子,并將9入棧。

??? stack = [3, 9]
??? index -> inorder[0] = 4

我們遍歷8,5和4。同理可得它們都是上一個節點(棧頂節點)的左兒子,所以它們會依次入棧。

??? stack = [3, 9, 8, 5, 4]
??? index -> inorder[0] = 4

我們遍歷10,這時情況就不一樣了。我們發現index恰好指向當前的棧頂節點4,也就是說4沒有左兒子,那么10必須為棧中某個節點的右兒子。那么如何找到這個節點呢?棧中的節點的順序和它們在前序遍歷中出現的順序是一致的,而且每一個節點的右兒子都還沒有被遍歷過,那么這些節點的順序和它們在中序遍歷中出現的順序一定是相反的。

這是因為棧中的任意兩個相鄰的節點,前者都是后者的某個祖先。并且我們知道,棧中的任意一個節點的右兒子還沒有被遍歷過,說明后者一定是前者左兒子的子樹中的節點,那么后者就先于前者出現在中序遍歷中。

??? 因此我們可以把index不斷向右移動,并與棧頂節點進行比較。如果index對應的元素恰好等于棧頂節點,那么說明我們在中序遍歷中找到了棧頂節點,所以將index增加1并彈出棧頂節點,直到index對應的元素不等于棧頂節點。按照這樣的過程,我們彈出的最后一個節點x就是10的雙親節點,這是因為10出現在了x與x在棧中的下一個節點的中序遍歷之間,因此10就是x的右兒子。

??? 回到我們的例子,我們會依次從棧頂彈出4,5和8,并且將index向右移動了三次。我們將10作為最后彈出的節點8的右兒子,并將10入棧。
??????? stack = [3, 9, 10]
??????? index -> inorder[3] = 10

??? 我們遍歷20。同理,index恰好指向當前棧頂節點10,那么我們會依次從棧頂彈出10,9和3,并且將index向右移動了三次。我們將20作為最后彈出的節點3的右兒子,并將20入棧。
??????? stack = [20]
??????? index -> inorder[6] = 15

??? 我們遍歷15,將15作為棧頂節點20的左兒子,并將15入棧。
??????? stack = [20, 15]
??????? index -> inorder[6] = 15

??? 我們遍歷7。index恰好指向當前棧頂節點15,那么我們會依次從棧頂彈出15和20,并且將index向右移動了兩次。我們將7作為最后彈出的節點20的右兒子,并將7入棧。
??????? stack = [7]
??????? index -> inorder[8] = 7

此時遍歷結束,我們就構造出了正確的二叉樹。我們歸納出上述例子中的算法流程:

??? 我們用一個棧和一個指針輔助進行二叉樹的構造。初始時棧中存放了根節點(前序遍歷的第一個節點),指針指向中序遍歷的第一個節點;

??? 我們依次枚舉前序遍歷中除了第一個節點以外的每個節點。如果index恰好指向棧頂節點,那么我們不斷地彈出棧頂節點并向右移動index,并將當前節點作為最后一個彈出的節點的右兒子;如果index和棧頂節點不同,我們將當前節點作為棧頂節點的左兒子;

??? 無論是哪一種情況,我們最后都將當前的節點入棧。

最后得到的二叉樹即為答案。如下為實現代碼:

class Solution {
public:TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {if (!preorder.size()) {return nullptr;}TreeNode* root = new TreeNode(preorder[0]);stack<TreeNode*> stk;stk.push(root);int inorderIndex = 0;for (int i = 1; i < preorder.size(); ++i) {int preorderVal = preorder[i];TreeNode* node = stk.top();if (node->val != inorder[inorderIndex]) {node->left = new TreeNode(preorderVal);stk.push(node->left);}else {while (!stk.empty() && stk.top()->val == inorder[inorderIndex]) {node = stk.top();stk.pop();++inorderIndex;}node->right = new TreeNode(preorderVal);stk.push(node->right);}}return root;}
};

筆者小記:

1、掌握二叉樹前序遍歷和中序遍歷的過程;

2、掌握二叉樹構建時,通過遞歸和迭代的方式添加二叉樹新節點的代碼書寫思路。

3、通過遞歸方式添加二叉樹新節點時有兩種方式,第一種:通過TreeNode*& root,引用的方式,root=new TreeNode(value),以及遞歸調用root->left和root->right傳入到void的遞歸嵌套函數中,并返回return。第二種:return返回值的形式,root->left=遞歸嵌套返回值,root->rigth=遞歸嵌套函數返回值,其中嵌套函數返回值類型為TreeNode。

4、前序遍歷和中序遍歷都可以通過來實現迭代方法,棧的作用是在遍歷過程中顯示地模擬遞歸調用的行為(顯示的遞歸棧)。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/70843.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/70843.shtml
英文地址,請注明出處:http://en.pswp.cn/web/70843.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Vue 3 + Vite 項目配置訪問地址到服務器某個文件夾的解決方案

前言 在開發 Vue 3 Vite 項目時&#xff0c;我們經常需要將項目部署到服務器的某個特定文件夾下。例如&#xff0c;將項目部署到 /my-folder/ 目錄下&#xff0c;而不是服務器的根目錄。這時&#xff0c;我們需要對 Vite 和 Vue Router 進行一些配置&#xff0c;以確保項目能…

【Rust中級教程】2.10. API設計原則之受約束性(constrained) Pt.1:對類型進行修改、`#[non_exhaustive]`注解

喜歡的話別忘了點贊、收藏加關注哦&#xff08;加關注即可閱讀全文&#xff09;&#xff0c;對接下來的教程有興趣的可以關注專欄。謝謝喵&#xff01;(&#xff65;ω&#xff65;) 2.10.1. 接口的更改要三思 如果你的接口要做出對用戶可見的更改&#xff0c;那么一定要三思…

Imagination GPU 3D Graphics Wrokload

本次分享Imagination GPU 的3D 圖像處理負載流程。 總的分為兩個階段 第一階段&#xff1a;Geometry Processing Phase&#xff08;幾何處理階段&#xff09;是渲染管線中的一個關鍵環節&#xff0c;主要負責對三維幾何數據進行處理和變換&#xff0c;以便后續在屏幕上進行顯…

自動化設備對接MES系統找DeepSeek問方案

項目需要現場的PLC設備HTTP協議JSON格式的方式對接MES系統平臺&#xff0c;于是試了一下&#xff1a; 找到的相關資源鏈接在這里。

VoIP之音頻3A技術

音頻3A技術是改善語音通話質量的三種關鍵技術的簡稱&#xff0c;包括聲學回聲消除&#xff08;Acoustic Echo Cancellation, AEC&#xff09;、自動增益控制&#xff08;Automatic Gain Control, AGC&#xff09;、自噪聲抑制&#xff08;Automatic Noise Suppression, ANS&…

量子計算的數學基礎:復數、矩陣和線性代數

量子計算是基于量子力學原理的一種新型計算模式,它與經典計算機在信息處理的方式上有著根本性的區別。在量子計算中,信息的最小單位是量子比特(qubit),而不是傳統計算中的比特。量子比特的狀態是通過量子力學中的數學工具來描述的,因此,理解量子計算的數學基礎對于深入學…

京準電鐘:NTP精密時鐘服務器在自動化系統中的作用

京準電鐘&#xff1a;NTP精密時鐘服務器在自動化系統中的作用 京準電鐘&#xff1a;NTP精密時鐘服務器在自動化系統中的作用 NTP精密時鐘服務器在自動化系統中的作用非常重要&#xff0c;特別是在需要高精度時間同步的場景中。NTP能夠提供毫秒級的時間同步精度&#xff0c;這…

Python實現GO鵝優化算法優化Catboost回歸模型項目實戰

說明&#xff1a;這是一個機器學習實戰項目&#xff08;附帶數據代碼文檔視頻講解&#xff09;&#xff0c;如需數據代碼文檔視頻講解可以直接到文章最后關注獲取。 1.項目背景 在當今的數據驅動時代&#xff0c;機器學習模型在各種應用中扮演著至關重要的角色。特別是在預測分…

如何在docker上部署前端nginx服務(VUE)

目錄結構 clean.sh docker stop rszWeb; docker rm rszWeb; start.sh docker run -d \ --name rszWeb \ -p 7084:80 \ -m 500m \ --privileged=true \ --restart=always \ -v /home/rsz/ui/conf/nginx.conf:/etc/nginx/nginx.conf \ -v /home/rsz/ui/logs:/meta/logs \ -v /…

可獄可囚的爬蟲系列課程 15:防盜鏈反爬蟲的處理

一、防盜鏈了解 防盜鏈是一種技術手段&#xff0c;主要用于防止其他網站通過直接鏈接的方式使用本網站的資源&#xff08;如圖片、文件等&#xff09;&#xff0c;從而節省帶寬和服務器資源。當其他網站嘗試直接鏈接到受保護的資源時&#xff0c;服務器會根據設置的規則判斷請求…

2020年藍橋杯Java B組第二場題目+部分個人解析

#A&#xff1a;門牌制作 624 解一&#xff1a; public static void main(String[] args) {int count0;for(int i1;i<2020;i) {int ni;while(n>0) {if(n%102) {count;}n/10;}}System.out.println(count);} 解二&#xff1a; public static void main(String[] args) {…

Hadoop架構詳解

Hadoop 是一個開源的分布式計算系統&#xff0c;用于存儲和處理大規模數據集。Hadoop 主要由HDFS&#xff08;Hadoop Distributed File System&#xff09;、MapReduce、Yarn&#xff08;Jobtracker&#xff0c;TaskTracker&#xff09;三大核心組件組成。其中HDFS是分布式文件…

DeepSeek在初創企業、教育和數字營銷領域應用思考

如今&#xff0c;像 DeepSeek 這樣的人工智能工具正在改變企業的運營方式&#xff0c;優化流程并顯著提高生產力。通過重復任務的自動化、大量數據的分析以及內容創建效率的提高&#xff0c;組織正在尋找新的競爭和卓越方式。本文介紹了 DeepSeek 如何用于提高三個關鍵領域的生…

day7作業

編寫一個如下場景&#xff1a; 有一個英雄Hero類&#xff0c;私有成員&#xff0c;攻擊&#xff08;Atx&#xff09;&#xff0c;防御&#xff08;Defense&#xff09;&#xff0c;速度&#xff08;Speed)&#xff0c;生命值&#xff08;Blood)&#xff0c;以及所有的set get 方…

阿里云ack的創建與實戰應用案例

阿里云ack的創建與應用案例 創建前開通ack相關服務&#xff1a;開始創建簡單的魔方游戲&#xff0c;熟悉sv與clb自動注冊創建部署一個nginx 服務示例&#xff1a;走不同域名訪問不同svc資源&#xff1a;為什么需要 Ingress &#xff1f;創建第一個域名的 Deployment和Service。…

青少年編程都有哪些比賽可以參加

Python小學生可參加的賽事&#xff1a; 電子學會青少年編程考級、中國計算機學會編程能力等級認證、藍橋杯、 信奧賽CSP-J/S初賽/NOIP(推薦C)、編程設計、信息素養、科技創新賽&#xff1b; 升學助力(科技特長生、大學)、企業、出國留學&#xff1b; python比賽&am…

MinIO在 Docker中修改登錄賬號和密碼

MinIO在 Docker中修改登錄賬號和密碼 隨著云計算和大數據技術的快速發展&#xff0c;對象存儲服務逐漸成為企業數據管理的重要組成部分。MinIO 作為一種高性能、分布式的對象存儲系統&#xff0c;因其簡單易用、高效可靠的特點而備受開發者青睞。然而&#xff0c;在實際應用中…

pycharm編寫ai大模型api調用程序及常見錯誤

這里寫目錄標題 一級目錄1. 訪問Django項目&#xff0c;python web url時&#xff0c;報錯2. 傳參報名&#xff0c;python web url時&#xff0c;報錯正確訪問結果&#xff1a; 二、購買價格 和 見錯誤碼 一級目錄 1. 訪問Django項目&#xff0c;python web url時&#xff0c;…

RISCV指令集解析

參考視頻&#xff1a;《RISC-V入門&進階教程》1-4-RV32I基本指令集&#xff08;1&#xff09;_嗶哩嗶哩_bilibili privilege是特權指令集&#xff0c;有點系統調用的感覺&#xff0c;要走內核態。unprivilege指令集有點像普通的函數調用。

Java中的TreeMap

TreeMap繼承自AbstractMap&#xff0c;并實現了NavigableMap接口(NavigableMap繼承自SortedMap接口)。底層的數據結構是紅黑樹&#xff0c;按照鍵的自然排序或者自定義實現的規則排序&#xff0c;實現元素的有序性。 特點 元素是有序的&#xff1a;按照key的自然排序或者是自…