leetcode654:最大二叉樹(遞歸與單調棧雙解法)

文章目錄

  • 一、 題目描述
  • 二、 核心思路:分而治之與遞歸構造
  • 三、代碼實現與深度解析
  • 四、 關鍵點與復雜度分析
  • 五、拓展解法
    • 單調棧解法
    • 兩種解法對比

LeetCode 654. 最大二叉樹,【難度:中等;通過率:82.6%】,這道題的描述本身就是一份遞歸的構造方式,我們的任務就是將這份描述優雅地 翻譯成代碼;且思路類似 “由中序與前序/后序遍歷結果構建二叉樹”,參考:

  • leetcode105深度解析:從前序與中序遍歷序列構造二叉樹
  • leetcode106深度解析:從中序與后序遍歷序列構造二叉樹

一、 題目描述

給定一個不含重復元素的整數數組 nums。一個以此數組直接遞歸構建的 最大二叉樹 定義如下:

  1. 二叉樹的根是數組 nums 中的最大元素
  2. 左子樹是通過數組中 最大值左邊部分 遞歸構造出的最大二叉樹
  3. 右子樹是通過數組中 最大值右邊部分 遞歸構造出的最大二叉樹

返回由 nums 構建的 最大二叉樹

示例:

示例 1:
示例 1

輸入: nums = [3,2,1,6,0,5]
輸出: [6,3,5,null,2,0,null,null,1]
解釋:
根節點是數組 [3,2,1,6,0,5] 中的最大值 6
左子樹是數組 [3,2,1] 構建出的最大二叉樹
右子樹是數組 [0,5] 構建出的最大二叉樹

示例 2:
示例 2

輸入: nums = [3,2,1]
輸出: [3,null,2,null,1]

二、 核心思路:分而治之與遞歸構造

題目的定義已經為我們指明了道路——遞歸。構建整棵樹的過程,和構建其任意子樹的過程,遵循著完全相同的規則,只是處理的數組范圍不同。這就是典型的“分而治之”思想

我們可以將構建過程分解為三步:

  1. 找到根節點:在當前需要處理的數組范圍內,找到最大值及其索引。這個最大值就是當前子樹的根節點
  2. 構建左子樹:對最大值左邊的子數組,遞歸地執行相同的構建過程,并將返回的根節點作為當前根節點的左孩子
  3. 構建右子樹:對最大值右邊的子數組,遞歸地執行相同的構建過程,并將返回的根節點作為當前根節點的右孩子

這個過程會一直持續下去,直到需要處理的子數組為空,此時返回 null,遞歸終止

三、代碼實現與深度解析

直觀解法就是定義一個輔助遞歸函數,該函數接收數組和需要處理的索引范圍 [left, right] 作為參數

class Solution {public TreeNode constructMaximumBinaryTree(int[] nums) {// 調用輔助遞歸函數,初始范圍為整個數組return build(nums, 0, nums.length - 1);}/*** 輔助遞歸函數:在指定的數組范圍 [left, right] 內構建最大二叉樹* @param nums  原始數組* @param left  當前處理范圍的左邊界(包含)* @param right 當前處理范圍的右邊界(包含)* @return 構建好的子樹的根節點*/private TreeNode build(int[] nums, int left, int right) {// 步驟 1: 定義遞歸終止條件// 如果左邊界大于右邊界,說明當前范圍為空,無法構建節點if (left > right) {return null;}// 步驟 2: 在當前范圍 [left, right] 內找到最大值的索引int maxIndex = -1;int maxValue = Integer.MIN_VALUE;for (int i = left; i <= right; i++) {if (nums[i] > maxValue) {maxValue = nums[i];maxIndex = i;}}// 步驟 3: 創建根節點TreeNode root = new TreeNode(maxValue);// 步驟 4: 遞歸構建左子樹// 左子樹的范圍是 [left, maxIndex - 1]root.left = build(nums, left, maxIndex - 1);// 步驟 5: 遞歸構建右子樹// 右子樹的范圍是 [maxIndex + 1, right]root.right = build(nums, maxIndex + 1, right);// 步驟 6: 返回當前構建好的根節點return root;}
}

四、 關鍵點與復雜度分析

  • 遞歸函數的定義build(nums, left, right) 的定義非常關鍵。它清晰地界定了每次遞歸需要處理的子問題——在 nums 數組的 [left, right] 區間內構建最大二叉樹
  • 尋找最大值:在每個遞歸步驟中,核心操作都是遍歷當前子數組以找到最大值。這是主要的耗時部分
  • 時間復雜度:在最壞的情況下(例如,數組是升序或降序排列的),樹會退化成一個鏈表。每次尋找最大值的操作都需要掃描近乎整個子數組,總的時間復雜度為 O(N) + O(N-1) + … + O(1) = O(N2)
  • 空間復雜度:空間開銷主要來自遞歸調用棧。棧的深度取決于樹的高度 H。在最壞情況下(退化為鏈表),樹的高度為 N,因此空間復雜度為 O(N)。在平均情況下(樹比較平衡),高度為 O(log N),空間復雜度為 O(log N)

五、拓展解法

這道題的樸素遞歸解法時間復雜度是 O(N2),有沒有優化的可能呢?

答案是肯定的。瓶頸在于每次都需要線性掃描來尋找最大值。如果我們能用更高效的方式在數組的一個區間內找到最大值,就能優化性能。例如,使用單調棧可以在 O(N) 的時間內完成整個構建過程。但這需要更復雜的邏輯,因為它將樹的構建過程從 “遞歸分解” 變為了 “迭代構建”,參考代碼如下:

單調棧解法

class Solution {public TreeNode constructMaximumBinaryTree(int[] nums) {if (nums == null || nums.length == 0) {return null;}// 使用數組(刷題環境顯然需要追求極致性能,別用Stack)模擬棧// 存儲 TreeNode 引用// 棧的大小最大為 nums.lengthTreeNode[] stack = new TreeNode[nums.length];int top = -1; // 棧頂指針,初始化為 -1 表示棧空for (int num : nums) {TreeNode node = new TreeNode(num); // 創建當前節點// 步驟 1: 處理棧頂元素 (pop)// 如果棧不為空,且棧頂元素的值小于當前節點的值// 那么棧頂元素應該成為當前節點的左孩子while (top != -1 && stack[top].val < node.val) {node.left = stack[top]; // 棧頂元素成為當前節點的左孩子top--; // 彈出棧頂元素}// 步驟 2: 連接當前節點的右孩子 (peek)// 如果棧不為空,且棧頂元素的值大于當前節點的值// 那么當前節點應該成為棧頂元素的右孩子if (top != -1) {stack[top].right = node; // 當前節點成為棧頂元素的右孩子}// 步驟 3: 當前節點入棧 (push)top++;stack[top] = node;}// 循環結束后,棧底(即 stack[0])的元素就是整棵樹的根節點// 因為它是最后一個入棧的,且沒有比它更大的元素讓它彈出或成為右孩子return stack[0]; }
}

兩種解法對比

遞歸構建 (樸素、直觀解法)單調棧 (O(N) 優化解法)
核心思路分而治之:找到當前范圍最大值作為根,遞歸處理左右子數組利用結構:通過維護一個單調遞減棧,一次遍歷確定每個節點的左右孩子
構建過程1. 遍歷當前子數組找最大值
2. 創建根節點
3. 遞歸構建左子樹
4. 遞歸構建右子樹
1. 遍歷數組元素,創建節點 curr
2. 棧頂小于 curr,彈出并作為 curr 的左孩子
3. 棧頂大于 currcurr 作為棧頂的右孩子
4. curr 入棧
時間復雜度O(N2):每次遞歸都可能線性掃描子數組找最大值O(N):每個元素最多入棧一次、出棧一次,哈希表操作平均 O(1)
空間復雜度O(N):遞歸棧深度最壞為 N (退化為鏈表)O(N):棧中最多存儲 N 個節點 (最壞情況)
代碼可讀性:與題目定義高度吻合,直觀易懂中等/低:邏輯較為抽象,需要理解單調棧的特性和指針連接規則
理解難度較低,適合初學者入門遞歸較高,需要對棧、樹結構和相對大小關系有深入理解
底層實現遞歸函數調用棧通常使用數組Deque (如 ArrayDeque) 模擬棧;不建議使用Stack
缺點效率低,可能導致超時邏輯復雜,不易一次性寫對

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

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

相關文章

Python 循環語法詳解

在編程中&#xff0c;循環是一種非常常見的控制結構。很多時候&#xff0c;我們需要重復做一些事情&#xff0c;比如遍歷列表、處理數據、嘗試直到成功等。這時候&#xff0c;就離不開循環了。Python 提供了兩種主要的循環結構&#xff1a;for 循環 和 while 循環。本篇文章會從…

一個小巧神奇的 USB數據線檢測儀

一個小巧的數據線檢測儀&#xff0c;檢測各種USB數據線是否損壞、通斷&#xff0c;TYPE_C、MICRO_B、蘋果線、燒錄線、網線都可檢測。嵌入式開發者的稱手工具。 這個是我個人制作的&#xff0c;SMT和連接器比較貴&#xff0c;特別是24PIN的C口連接器&#xff0c;我掛在黃色小魚…

37.【.NET8 實戰--孢子記賬--從單體到微服務--轉向微服務】--擴展功能--增加Github Action

在第二部分&#xff08;微服務基礎工具與技術&#xff09;中我們講解了GitHub Action的相關知識&#xff0c;那么在這一節中&#xff0c;我們將為已有的微服務增加GitHub Action的支持。 一、什么是GitHub Action 雖然前面已經介紹過GitHub Action的相關知識&#xff0c;但這里…

ROS2 通過 命令行 發布速度控制指令 控制 麥克娜姆輪

在 ROS2 中&#xff0c;要通過命令行發布速度控制指令來控制麥克娜姆輪機器人&#xff0c;你需要知道機器人所使用的速度控制話題和消息類型。通常麥克娜姆輪機器人使用geometry_msgs/Twist消息類型來接收速度指令。 以下是通過命令行發布速度控制指令的方法&#xff1a; 首先確…

多層Model更新多層ListView

一、總體架構QML (三層 ListView)└─ C 單例 DataCenter (QQmlContext 注冊)├─ L1Model (一級節點)│ └─ 內部持有 QList<L2Model*>│ └─ L2Model (二級節點)│ └─ 內部持有 QList<L3Model*>│ └─ L3Model (三級節…

Git基礎操作教程

本文目的是掌握Git基礎操作教程一、Git簡介Git&#xff1a;分布式版本控制系統&#xff0c;使用倉庫(Repository)來記錄文件的變化最流行的版本控制系統有兩種&#xff1a;集中式&#xff08;SVN&#xff09;、分布式&#xff08;Git&#xff09;二、Git操作1.創建倉庫倉庫(Rep…

Android 之 Kotlin

變量變量的聲明Kotlin使用var&#xff0c;val來聲明變量&#xff0c;注意&#xff1a;Kotlin不再需要;來結尾var 可變變量&#xff0c;對應java的非final變量var b 1val不可變變量&#xff0c;對應java的final變量val a 1兩種變量并未聲明類型&#xff0c;這是因為Kotlin存在…

Design Compiler:布圖規劃探索(ICC)

相關閱讀 Design Compilerhttps://blog.csdn.net/weixin_45791458/category_12738116.html?spm1001.2014.3001.5482 簡介 在Design Compiler Graphical中&#xff0c;可以用布圖規劃探索(Floorplan Exploration)功能&#xff0c;打開IC Compiler進行布圖規劃的創建、修改與分…

《藍牙低功耗音頻技術架構解析》

《2025GAS聲學大講堂—音頻產業創新技術公益講座》低功耗藍牙音頻系列專題LE Audio & Auracast?專題講座第1講將于8月7日周四19點開講&#xff0c;本次邀請了藍牙技術聯盟 技術與市場經理 魯公羽 演講&#xff0c;講座主題&#xff1a;《藍牙低功耗音頻技術架構解析》。&…

ubuntu apt安裝與dpkg安裝相互之間的關系

0. 問題解釋 在linux系統中&#xff0c;使用neofetch命令可以看到現在系統中使用dpkg, flatpak, snap安裝的包的數量&#xff0c;那么使用apt安裝的包被統計在什么位置了呢&#xff0c;使用apt的安裝流程和使用flatpak的安裝流程有什么關系和區別呢?1. apt 安裝的包在哪里&…

YooAsset源碼閱讀-Downloader篇

YooAsset源碼閱讀-Downloader 繼續 YooAsset 的 Downloader &#xff0c;本文將詳細介紹如何創建下載器相關代碼 CreateResourceDownloaderByAll 關鍵類 PlayModeImpl.csResourceDownloaderOperation.csDownloaderOperation.csBundleInfo.cs CreateResourceDownloaderByAll 方法…

豆包新模型與 PromptPilot 實操體驗測評,AI 輔助創作的新范式探索

摘要&#xff1a;在 AI 技術飛速發展的當下&#xff0c;各類大模型及輔助工具層出不窮&#xff0c;為開發者和創作者帶來了全新的體驗。2025 年 7 月 30 日廈門站的火山方舟線下 Meetup&#xff0c;為我們提供了近距離接觸豆包新模型與 PromptPilot 的機會。本次重點體驗了實驗…

深入探討AI在測試領域的三大核心應用:自動化測試框架、智能缺陷檢測和A/B測試優化,并通過代碼示例、流程圖和圖表詳細解析其實現原理和應用場景。

引言隨著人工智能技術的飛速發展&#xff0c;軟件測試領域正在經歷一場深刻的變革。AI技術不僅提高了測試效率&#xff0c;還增強了測試的準確性和覆蓋范圍。本文將深入探討AI在測試領域的三大核心應用&#xff1a;自動化測試框架、智能缺陷檢測和A/B測試優化&#xff0c;并通過…

音視頻學習筆記

0.vs應用其他庫配置1基礎 1.1視頻基礎 音視頻錄制原理音視頻播放原理圖像表示rgb圖像表示yuvhttps://blog.51cto.com/u_7335580/2059670 https://blog.51cto.com/cto521/1944224 https://blog.csdn.net/mandagod/article/details/78605586?locationNum7&fps1 視頻主要概念…

LLM隱藏層狀態: outputs.hidden_states 是 MLP Residual 還是 Layer Norm

outputs.hidden_states 是 MLP Residual 還是 Layer Norm outputs.hidden_states 既不是單純的 MLP Residual,也不是單純的 Layer Norm,而是每一層所有組件(包括 Layer Norm、注意力、MLP、殘差連接等)處理后的最終隱藏狀態。具體需結合 Transformer 層的結構理解: 1. T…

XML 用途

XML 用途 引言 XML&#xff08;可擴展標記語言&#xff09;是一種用于存儲和傳輸數據的標記語言。自1998年推出以來&#xff0c;XML因其靈活性和可擴展性&#xff0c;在眾多領域得到了廣泛應用。本文將詳細介紹XML的用途&#xff0c;幫助讀者全面了解這一重要技術。 一、數據存…

亞馬遜撤離Google購物廣告:重構流量生態的戰略博弈

戰略突變&#xff1a;從漸進收縮到全面退潮的背后邏輯亞馬遜在2025年7月突然全面停止Google Shopping廣告投放&#xff0c;這場看似 abrupt 的決策實則經歷了一年多的戰略鋪墊&#xff0c;從2024年Q1開始的預算削減&#xff0c;到2025年Q2美國市場支出減半&#xff0c;直至核心…

【QT】常?控件詳解(三)常用按鈕控件PushButton RadioButton CheckButton Tool Button

文章目錄前言一、PushButton1.1 QAbstractButton1.2 添加圖標的按鈕1.3 給按鈕添加快捷鍵1.4 代碼?例:按鈕的重復觸發二、 RadioButtion2.1簡介2.2 幾個槽函數 click,press,release, toggled 的區別2.2 模擬分組點餐三、 CheckBox四、Tool Button&#x1f6a9;總結前言 一、P…

數據結構:反轉鏈表(reverse the linked list)

目錄 通過交換元素值實現反轉&#xff08;reverse by swapping elements&#xff09; 滑動指針&#xff08;sliding pointers&#xff09; 使用滑動指針反轉鏈表&#xff08;Reversing a Linked List using Sliding Pointers&#xff09; 對比分析 如何用遞歸&#xff08;R…

【C#】基于SharpCompress實現壓縮包解壓功能

1.SharpCompress安裝 在vs的nuget下搜索安裝SharpCompress&#xff0c;如圖所示2.解壓縮包功能實現 /// <summary> /// 解壓壓縮包 /// </summary> /// <param name"filePath">壓縮包文件路徑</param> /// <param name"directoryPat…