91在线观看视频-91在线观看视频-91在线观看免费视频-91在线观看免费-欧美第二页-欧美第1页

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

二叉樹的所有路徑介紹

新材料在線 ? 來源:代碼隨想錄 ? 作者:程序員Carl ? 2021-08-13 17:51 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

以為只用了遞歸,其實還用了回溯

257. 二叉樹的所有路徑

題目地址:https://leetcode-cn.com/problems/binary-tree-paths/

給定一個二叉樹,返回所有從根節點到葉子節點的路徑。

說明: 葉子節點是指沒有子節點的節點。

思路

這道題目要求從根節點到葉子的路徑,所以需要前序遍歷,這樣才方便讓父節點指向孩子節點,找到對應的路徑。

在這道題目中將第一次涉及到回溯,因為我們要把路徑記錄下來,需要回溯來回退一一個路徑在進入另一個路徑。

前序遍歷以及回溯的過程如圖:

07b5afe6-fbbe-11eb-9bcf-12bb97331649.png

我們先使用遞歸的方式,來做前序遍歷。要知道遞歸和回溯就是一家的,本題也需要回溯。

遞歸

遞歸函數函數參數以及返回值

要傳入根節點,記錄每一條路徑的path,和存放結果集的result,這里遞歸不需要返回值,代碼如下:

void traversal(TreeNode* cur, vector《int》& path, vector《string》& result)

確定遞歸終止條件

再寫遞歸的時候都習慣了這么寫:

if (cur == NULL) {

終止處理邏輯

}

但是本題的終止條件這樣寫會很麻煩,因為本題要找到葉子節點,就開始結束的處理邏輯了(把路徑放進result里)。

那么什么時候算是找到了葉子節點? 是當 cur不為空,其左右孩子都為空的時候,就找到葉子節點。

所以本題的終止條件是:

if (cur-》left == NULL && cur-》right == NULL) {

終止處理邏輯

}

為什么沒有判斷cur是否為空呢,因為下面的邏輯可以控制空節點不入循環。

再來看一下終止處理的邏輯。

這里使用vector結構path來記錄路徑,所以要把vector結構的path轉為string格式,在把這個string 放進 result里。

那么為什么使用了vector結構來記錄路徑呢? 因為在下面處理單層遞歸邏輯的時候,要做回溯,使用vector方便來做回溯。

可能有的同學問了,我看有些人的代碼也沒有回溯啊。

其實是有回溯的,只不過隱藏在函數調用時的參數賦值里,下文我還會提到。

這里我們先使用vector結構的path容器來記錄路徑,那么終止處理邏輯如下:

if (cur-》left == NULL && cur-》right == NULL) { // 遇到葉子節點

string sPath;

for (int i = 0; i 《 path.size() - 1; i++) { // 將path里記錄的路徑轉為string格式

sPath += to_string(path[i]);

sPath += “-》”;

}

sPath += to_string(path[path.size() - 1]); // 記錄最后一個節點(葉子節點)

result.push_back(sPath); // 收集一個路徑

return;

}

確定單層遞歸邏輯

因為是前序遍歷,需要先處理中間節點,中間節點就是我們要記錄路徑上的節點,先放進path中。

path.push_back(cur-》val);

然后是遞歸和回溯的過程,上面說過沒有判斷cur是否為空,那么在這里遞歸的時候,如果為空就不進行下一層遞歸了。

所以遞歸前要加上判斷語句,下面要遞歸的節點是否為空,如下

if (cur-》left) {

traversal(cur-》left, path, result);

}

if (cur-》right) {

traversal(cur-》right, path, result);

}

此時還沒完,遞歸完,要做回溯啊,因為path 不能一直加入節點,它還要刪節點,然后才能加入新的節點。

那么回溯要怎么回溯呢,一些同學會這么寫,如下:

if (cur-》left) {

traversal(cur-》left, path, result);

}

if (cur-》right) {

traversal(cur-》right, path, result);

}

path.pop_back();

這個回溯就要很大的問題,我們知道,回溯和遞歸是一一對應的,有一個遞歸,就要有一個回溯,這么寫的話相當于把遞歸和回溯拆開了, 一個在花括號里,一個在花括號外。

所以回溯要和遞歸永遠在一起,世界上最遙遠的距離是你在花括號里,而我在花括號外!

那么代碼應該這么寫:

if (cur-》left) {

traversal(cur-》left, path, result);

path.pop_back(); // 回溯

}

if (cur-》right) {

traversal(cur-》right, path, result);

path.pop_back(); // 回溯

}

那么本題整體代碼如下:

class Solution {private:

void traversal(TreeNode* cur, vector《int》& path, vector《string》& result) {

path.push_back(cur-》val);

// 這才到了葉子節點

if (cur-》left == NULL && cur-》right == NULL) {

string sPath;

for (int i = 0; i 《 path.size() - 1; i++) {

sPath += to_string(path[i]);

sPath += “-》”;

}

sPath += to_string(path[path.size() - 1]);

result.push_back(sPath);

return;

}

if (cur-》left) {

traversal(cur-》left, path, result);

path.pop_back(); // 回溯

}

if (cur-》right) {

traversal(cur-》right, path, result);

path.pop_back(); // 回溯

}

}

public:

vector《string》 binaryTreePaths(TreeNode* root) {

vector《string》 result;

vector《int》 path;

if (root == NULL) return result;

traversal(root, path, result);

return result;

}

};

如上的C++代碼充分體現了回溯。

那么如上代碼可以精簡成如下代碼:

class Solution {private:

void traversal(TreeNode* cur, string path, vector《string》& result) {

path += to_string(cur-》val); // 中

if (cur-》left == NULL && cur-》right == NULL) {

result.push_back(path);

return;

}

if (cur-》left) traversal(cur-》left, path + “-》”, result); // 左

if (cur-》right) traversal(cur-》right, path + “-》”, result); // 右

}

public:

vector《string》 binaryTreePaths(TreeNode* root) {

vector《string》 result;

string path;

if (root == NULL) return result;

traversal(root, path, result);

return result;

}

};

如上代碼精簡了不少,也隱藏了不少東西。

注意在函數定義的時候void traversal(TreeNode* cur, string path, vector《string》& result) ,定義的是string path,每次都是復制賦值,不用使用引用,否則就無法做到回溯的效果。

那么在如上代碼中,貌似沒有看到回溯的邏輯,其實不然,回溯就隱藏在traversal(cur-》left, path + “-》”, result);中的 path + “-》”。 每次函數調用完,path依然是沒有加上“-》” 的,這就是回溯了。

為了把這份精簡代碼的回溯過程展現出來,大家可以試一試把:

if (cur-》left) traversal(cur-》left, path + “-》”, result); // 左 回溯就隱藏在這里

改成如下代碼:

path += “-》”;

traversal(cur-》left, path, result); // 左

即:

if (cur-》left) {

path += “-》”;

traversal(cur-》left, path, result); // 左

}

if (cur-》right) {

path += “-》”;

traversal(cur-》right, path, result); // 右

}

此時就沒有回溯了,這個代碼就是通過不了的了。

如果想把回溯加上,就要 在上面代碼的基礎上,加上回溯,就可以AC了。

if (cur-》left) {

path += “-》”;

traversal(cur-》left, path, result); // 左

path.pop_back(); // 回溯

path.pop_back();

}

if (cur-》right) {

path += “-》”;

traversal(cur-》right, path, result); // 右

path.pop_back(); // 回溯

path.pop_back();

}

大家應該可以感受出來,如果把 path + “-》”作為函數參數就是可以的,因為并有沒有改變path的數值,執行完遞歸函數之后,path依然是之前的數值(相當于回溯了)

綜合以上,第二種遞歸的代碼雖然精簡但把很多重要的點隱藏在了代碼細節里,第一種遞歸寫法雖然代碼多一些,但是把每一個邏輯處理都完整的展現了出來了。

迭代法

至于非遞歸的方式,我們可以依然可以使用前序遍歷的迭代方式來模擬遍歷路徑的過程,對該迭代方式不了解的同學,可以看文章二叉樹:聽說遞歸能做的,棧也能做!和二叉樹:前中后序迭代方式統一寫法。

這里除了模擬遞歸需要一個棧,同時還需要一個棧來存放對應的遍歷路徑。

C++代碼如下:

class Solution {public:

vector《string》 binaryTreePaths(TreeNode* root) {

stack《TreeNode*》 treeSt;// 保存樹的遍歷節點

stack《string》 pathSt; // 保存遍歷路徑的節點

vector《string》 result; // 保存最終路徑集合

if (root == NULL) return result;

treeSt.push(root);

pathSt.push(to_string(root-》val));

while (!treeSt.empty()) {

TreeNode* node = treeSt.top(); treeSt.pop(); // 取出節點 中

string path = pathSt.top();pathSt.pop(); // 取出該節點對應的路徑

if (node-》left == NULL && node-》right == NULL) { // 遇到葉子節點

result.push_back(path);

}

if (node-》right) { // 右

treeSt.push(node-》right);

pathSt.push(path + “-》” + to_string(node-》right-》val));

}

if (node-》left) { // 左

treeSt.push(node-》left);

pathSt.push(path + “-》” + to_string(node-》left-》val));

}

}

return result;

}

};

當然,使用java的同學,可以直接定義一個成員變量為object的棧Stack《Object》 stack = new Stack《》();,這樣就不用定義兩個棧了,都放到一個棧里就可以了。

總結

本文我們開始初步涉及到了回溯,很多同學過了這道題目,可能都不知道自己其實使用了回溯,回溯和遞歸都是相伴相生的。

我在第一版遞歸代碼中,把遞歸與回溯的細節都充分的展現了出來,大家可以自己感受一下。

第二版遞歸代碼對于初學者其實非常不友好,代碼看上去簡單,但是隱藏細節于無形。

最后我依然給出了迭代法。

對于本地充分了解遞歸與回溯的過程之后,有精力的同學可以在去實現迭代法。

其他語言版本

Java:

//解法一class Solution {

/**

* 遞歸法

*/

public List《String》 binaryTreePaths(TreeNode root) {

List《String》 res = new ArrayList《》();

if (root == null) {

return res;

}

List《Integer》 paths = new ArrayList《》();

traversal(root, paths, res);

return res;

}

private void traversal(TreeNode root, List《Integer》 paths, List《String》 res) {

paths.add(root.val);

// 葉子結點

if (root.left == null && root.right == null) {

// 輸出

StringBuilder sb = new StringBuilder();

for (int i = 0; i 《 paths.size() - 1; i++) {

sb.append(paths.get(i)).append(“-》”);

}

sb.append(paths.get(paths.size() - 1));

res.add(sb.toString());

return;

}

if (root.left != null) {

traversal(root.left, paths, res);

paths.remove(paths.size() - 1);// 回溯

}

if (root.right != null) {

traversal(root.right, paths, res);

paths.remove(paths.size() - 1);// 回溯

}

}

}

Python

class Solution:

def binaryTreePaths(self, root: TreeNode) -》 List[str]:

path=[]

res=[]

def backtrace(root, path):

if not root:return

path.append(root.val)

if (not root.left)and (not root.right):

res.append(path[:])

ways=[]

if root.left:ways.append(root.left)

if root.right:ways.append(root.right)

for way in ways:

backtrace(way,path)

path.pop()

backtrace(root,path)

return [“-》”.join(list(map(str,i))) for i in res]

Go:

func binaryTreePaths(root *TreeNode) []string {

res := make([]string, 0)

var travel func(node *TreeNode, s string)

travel = func(node *TreeNode, s string) {

if node.Left == nil && node.Right == nil {

v := s + strconv.Itoa(node.Val)

res = append(res, v)

return

}

s = s + strconv.Itoa(node.Val) + “-》”

if node.Left != nil {

travel(node.Left, s)

}

if node.Right != nil {

travel(node.Right, s)

}

}

travel(root, “”)

return res

}

責任編輯:haq

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 函數
    +關注

    關注

    3

    文章

    4381

    瀏覽量

    64881
  • 二叉樹
    +關注

    關注

    0

    文章

    74

    瀏覽量

    12639

原文標題:二叉樹的所有路徑:不止遞歸,還有回溯

文章出處:【微信號:xincailiaozaixian,微信公眾號:新材料在線】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

    評論

    相關推薦
    熱點推薦

    億緯鋰能榮獲杭集團2022-2024年度優秀供應商獎

    近日,億緯鋰能憑借卓越產品、可靠交付與優質服務榮獲杭集團頒發的“2022-2024年度優秀供應商”獎。杭集團副總經理兼杭電器董事長金華曙、杭電器總經理兼杭博電機總經理李明輝出席
    的頭像 發表于 07-15 09:00 ?261次閱讀

    想在rtsmart中使用uart2,是不是只能通過修改設備方法來實現uart2的復用呀?

    我想在rtsmart中使用uart2,是不是只能通過修改設備方法來實現uart2的復用呀? 修改設備后如何只編譯設備文件? 編譯生成的文件可以直接替換到廬山派里嗎,具體替換路徑
    發表于 06-24 07:04

    電源工程師的核心技能體系

    電源工程師的核心技能體系需覆蓋從基礎理論到專業實踐、工具應用及行業適配的全鏈條能力。以下是系統化的技能框架,按知識層級和應用場景展開,幫助從業者明確能力提升路徑: 一、基礎理論層:核心知識根基
    的頭像 發表于 06-05 09:44 ?657次閱讀

    機器人看點:宇科技王興興回上海母校 加速商業化落地 宇機器人手租賃火爆

    給大家帶來一些機器人的消息: 宇科技王興興回上海母校 加速商業化落地 日前,宇科技創始人王興興在接受媒體專訪時候,介紹了公司的H1人形機器人的技術亮點及行業前景,H1人形機器人是首款能原地后翻出
    的頭像 發表于 02-25 11:26 ?1430次閱讀

    求解答,設備問題

    請問,rk3588j要再提取一個USB3.0接口設備怎么改
    發表于 02-20 11:22

    科技在物聯網方面

    科技在物聯網領域有多方面的涉及和發展,以下是一些具體信息: 傳感器技術合作 與傳感器公司合作:宇科技與一些傳感器技術公司有合作,例如奧比中光為宇機器狗提供激光雷達及結構光傳感器,這些傳感器
    發表于 02-04 06:48

    飛凌嵌入式ElfBoard ELF 1板卡-初識設備之Makefile修改

    不同而新增加了dts,則需要在這個Makefile的這個位置添加上對應的.dtb文件名參與編譯。ELF 1使用的設備命名為imx6ull-elf1-emmc.dts,是基于NXP官方evk板子的設備imx6ull-14x14-evk.dts修改而來,修改的內容及方法將
    發表于 01-10 09:23

    FRED應用:階鬼像分析

    設計文件準備鬼像分析的過程,介紹了能夠幫助自動運行階鬼像分析的一個腳本工具。腳本運行后,使FRED文件包含了系統中所有階鬼像路徑的結果,
    發表于 01-10 08:55

    嵌入式學習-飛凌嵌入式ElfBoard ELF 1板卡-初識設備之設備組成和結構

    的name和value。在設備中,可描述的信息包括:一、CPU的數量和類別;、內存基地址和大小;三、總線和橋;四、外設連接;五、中斷控制器和中斷使用情況;六、GPIO控制器和GPIO使用情況;七
    發表于 01-08 08:32

    飛凌嵌入式ElfBoard ELF 1板卡-初識設備之設備組成和結構

    的name和value。在設備中,可描述的信息包括:一、CPU的數量和類別;、內存基地址和大小;三、總線和橋;四、外設連接;五、中斷控制器和中斷使用情況;六、GPIO控制器和GPIO使用情況;七
    發表于 01-07 09:16

    一千余字解讀stm32時鐘

    轉換為多個外部設備的周期性運作。這種時鐘“能量”的傳遞路徑類似于大樹的養分由主干流向各個分支,因此被稱為時鐘。STM32內部也是由多種多樣的電路模塊組合在一起實現
    的頭像 發表于 12-30 21:01 ?2812次閱讀
    一千余字解讀stm32時鐘<b class='flag-5'>樹</b>

    光學中簡單但重要的光學路徑與成像系統介紹

    ? 本文簡單介紹了光學一些簡單但重要的光學路徑與成像系統。 ? 光在物質中傳播得更慢:折射率n=c/v ? ? ? 透鏡通過折射原理工作: ? ? 傳播方向與波前垂直: ? ? 單透鏡成像
    的頭像 發表于 12-30 13:55 ?749次閱讀
    光學中簡單但重要的光學<b class='flag-5'>路徑</b>與成像系統<b class='flag-5'>介紹</b>

    什么是默克爾(Merkle Tree)?如何計算默克爾根?

    01 默克爾的概念 默克爾(Merkle Tree)是一種特殊的二叉樹,它的每個節點都存儲了一個數據塊的哈希值。哈希值是一種可以將任意長度的數據轉換為固定長度的字符串的算法,它具有唯一性和不可
    的頭像 發表于 09-30 18:22 ?2362次閱讀
    什么是默克爾<b class='flag-5'>樹</b>(Merkle Tree)?如何計算默克爾根?

    SD-WAN技術在直播網絡中如何實現智能路徑選擇?

    SD-WAN技術在直播網絡中實現智能路徑選擇主要通過以下幾個步驟: 1、實時網絡監控:SD-WAN系統持續監控所有可用的網絡路徑,包括它們的帶寬、延遲、丟包率和抖動等關鍵性能指標。 2、路徑
    的頭像 發表于 09-09 14:39 ?672次閱讀

    INA228-Q1防反接設計遇到的疑問求解

    1.2V的壓降(MCU_GND-&gt;INA228_GND)。我們斷開了其他所有路徑,這個現象仍然存在,結合極管隔離后的作用,我們推斷這個路徑來自INA228,但不太理解這其中的原因。 希望能得到你們的解答,感謝!
    發表于 07-30 06:37
    主站蜘蛛池模板: 操日本美女视频 | 最新亚洲一区二区三区四区 | 特黄一级 | 午夜片 飘香香影院 | 亚洲婷婷综合色高清在线 | 日本系列 1页 亚洲系列 | 亚洲成年人免费网站 | 啪啪在线视频 | 久久99精品久久久久久牛牛影视 | 色视频在线观看完整免费版 | 美女视频黄a视频美女大全 美女视频一区二区 | 色综合网址 | 国产高清一区二区三区四区 | 2017天天操| 日韩一区二区三区免费 | 屁屁影院在线 | 性xxx无遮挡 | 在线观看免费视频一区 | 人人精品久久 | 免费看欧美一级特黄α大片 | 伊人网成人 | 欧美性喷潮xxxx | 免费在线观看大片影视大全 | 男女视频在线观看免费高清观看 | 1区2区3区4区 | 色噜噜狠狠成人中文小说 | 91啪免费网站在线观看 | 久久免费99精品久久久久久 | 黄色大片毛片 | 亚洲伊人久久大香线蕉结合 | 黄 色 免 费 网站在线观看 | 成人国产日本亚洲精品 | 一级骚片超级骚在线观看 | 亚洲三级视频 | 激情五月宗合网 | 国产叼嘿网站免费观看不用充会员 | 狠狠干2019| 天堂在线中文无弹窗全文阅读 | 成 年 人 视频在线播放 | 四虎最新网站 | 色色色色色色色色色色色色色色 |