通关算法题之 ⌈二叉树⌋ 上
创始人
2024-04-10 18:30:16

二叉树深度

104、求二叉树最大深度

二叉树的深度为根节点到最远叶子节点的最长路径上的节点数,叶子节点是指没有子节点的节点。

示例:
给定二叉树[3,9,20,null,null,15,7]

    3/ \9  20/  \15   7

返回它的最大深度 3。

解法一:递归遍历二叉树,回溯算法思路

遍历一遍二叉树,用一个外部变量记录每个节点所在的深度,取最大值就可以得到最大深度。

class Solution {
public:int depth = 0;int res = 0;int maxDepth(TreeNode* root) {traverse(root);return res;}//遍历二叉树void traverse(TreeNode* root){if(root == nullptr){return;}//前序遍历位置depth++;//遍历过程中记录最大深度res = max(depth, res);traverse(root->left);traverse(root->right);//后序遍历位置depth--;}
};

解法二:分解成子树问题,动态规划思路

class Solution {
public:// 定义:输入一个节点,返回以该节点为根的二叉树的最大深度int maxDepth(TreeNode* root) {if(root == nullptr){return 0;}int leftDepth = maxDepth(root->left);int rightDepth = maxDepth(root->right);// 根据左右子树的最大深度推出原二叉树的最大深度return max(leftDepth, rightDepth) + 1;}
};

解法三:层序遍历

class Solution {
public:int maxDepth(TreeNode* root) {int depth = 0;queue que;if(root) que.push(root);while(!que.empty()){int size = que.size();depth++;for(int i = 0; i < size; i++){TreeNode* node = que.front();que.pop();if(node->left) que.push(node->left);if(node->right) que.push(node->right);}}return depth;}
};

求二叉树的最大深度可以延伸到求二叉树的直径:

543、二叉树直径

给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。

示例 :
给定二叉树

          1/ \2   3/ \     4   5    

返回 3, 它的长度是路径 [4,2,1,3] 或者 [5,2,1,3]。

每一条二叉树的「直径」长度,就是一个节点的左右子树的最大深度之和。把计算「直径」的逻辑放在后序位置,准确说应该是放在 maxDepth 的后序位置,因为 maxDepth 的后序位置是知道左右子树的最大深度的。

class Solution {
public:int maxDiameter = 0;int diameterOfBinaryTree(TreeNode* root) {maxDepth(root);return maxDiameter;       }//定义:输入一个节点,返回以该节点为根节点的二叉树的深度int maxDepth(TreeNode* root){if(root == nullptr){return 0;}int leftDepth = maxDepth(root->left);int rightDepth = maxDepth(root->right);// 后序位置,顺便计算最大直径maxDiameter = max(leftDepth + rightDepth, maxDiameter);return max(leftDepth, rightDepth) + 1;}
};

111、二叉树的最小深度

给定一个二叉树,找出其最小深度。最小深度是从根节点到最近叶子节点的最短路径上的节点数量。

示例:给定二叉树[3,9,20,null,null,15,7]

      3/ \9  20/ \     15  7    

返回它的最小深度2。

解法一:分解成子树问题,动态规划思想

class Solution {
public://定义:输入一个节点,返回以该节点为根节点的二叉树的最小深度int minDepth(TreeNode* root) {if(!root) {return 0;}int leftDepth = minDepth(root->left);int rightDepth = minDepth(root->right);return min(leftDepth, rightDepth) + 1;        }    
};

上面的算法对吗?为什么?

❎错误!

正确的解法一:分解成子树问题,动态规划思想

class Solution {
public:int minDepth(TreeNode* root) {if(!root){return 0;}int leftDepth = minDepth(root->left);int rightDepth = minDepth(root->right);if(!root->left){return rightDepth + 1;}if(!root->right){return leftDepth + 1;}return min(leftDepth, rightDepth) + 1;}
};

解法二:层序遍历

class Solution {
public:int minDepth(TreeNode* root) {int depth = 0;queue que;if(root) que.push(root);while(!que.empty()){int size = que.size();depth++;for(int i = 0; i < size; i++){TreeNode* node = que.front();que.pop();if(!node->left && !node->right) return depth;if(node->left) que.push(node->left);if(node->right) que.push(node->right);}}return depth;}
};

110、平衡二叉树

给定一个二叉树,判断它是否是高度平衡的二叉树。

本题中,一棵高度平衡二叉树定义为:

一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。

示例 :

在这里插入图片描述

输入:root = [3,9,20,null,null,15,7]
输出:true

本题依然是二叉树深度相关的题目,依然是把计算「高度差」的逻辑放在后序位置,准确说应该是放在 maxDepth 的后序位置,因为 maxDepth 的后序位置是知道左右子树的最大深度的。

只计算一次最大深度,计算的过程中在后序遍历位置顺便判断二叉树是否平衡:对于每个节点,先算出来左右子树的最大高度,然后在后序遍历的位置根据左右子树的最大高度判断平衡性。

class Solution {
public:// 记录二叉树是否平衡bool balance = true;bool isBalanced(TreeNode* root) {maxDepth(root);return balance;}// 定义:输入一个节点,返回以该节点为根的二叉树的最大深度int maxDepth(TreeNode* root){if(!root) return 0;int leftDepth= maxDepth(root->left);int rightDepth = maxDepth(root->right);if(abs(leftDepth - rightDepth) > 1) balance = false;return max(leftDepth, rightDepth) + 1;}
};

翻转二叉树

226、翻转二叉树

给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。

示例 1:

在这里插入图片描述

输入:root = [4,2,7,1,3,6,9]
输出:[4,7,2,9,6,3,1]

解法一:递归遍历,回溯算法的思想

遍历二叉树的每个节点,每个节点的左、右子树交换位置。

class Solution {
public:TreeNode* invertTree(TreeNode* root) {traverse(root);return root;}// 二叉树遍历函数void traverse(TreeNode* root){if(!root){return;}// 每一个节点需要做的事就是交换它的左右子节点TreeNode* tmp = root->left;root->left = root->right;root->right = tmp;// 遍历框架,去遍历左右子树的节点traverse(root->left);traverse(root->right);}
};

解法二:分解为子树的问题,动态规划思想

invertTree(x.left) 先把 x 的左子树翻转,再用 invertTree(x.right)x 的右子树翻转,最后把 x 的左右子树交换,这恰好完成了以 x 为根的整棵二叉树的翻转,即完成了 invertTree(x) 的定义。

class Solution {
public://定义:输入一个节点,将以该节点为根节点的二叉树进行翻转,返回其根节点TreeNode* invertTree(TreeNode* root) {if(!root) {return root;}// 利用函数定义,先翻转左右子树TreeNode* left = invertTree(root->left);TreeNode* right = invertTree(root->right);// 然后交换左右子节点root->left = right;root->right = left;return root;}    
};

解法三:层序遍历

class Solution {
public:TreeNode* invertTree(TreeNode* root) {if (root == NULL) return root;stack st;st.push(root);while(!st.empty()) {TreeNode* node = st.top();              // 中st.pop();swap(node->left, node->right);if(node->right) st.push(node->right);   // 右if(node->left) st.push(node->left);     // 左}return root;}
};

路径总和

112、路径总和

给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。如果存在,返回 true ;否则,返回 false

示例:

在这里插入图片描述

输入:root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22
输出:true
解释:等于目标和的根节点到叶节点路径如上图所示。

解法一:递归遍历,回溯算法思想

前部遍历位置(进入节点)sum += root->val,顺便判断是否到达叶子节点且和为targetSum;后序遍历位置(离开节点)sum -= root->val

class Solution {
public:bool res = false;int sum = 0;bool hasPathSum(TreeNode* root, int targetSum) {traverse(root, targetSum);return res;}void traverse(TreeNode* root, int targetSum){if(!root) return;//前序遍历位置sum += root->val;// 到达叶子节点且和为targetSumif(!root->left && !root->right && sum == targetSum) res = true;traverse(root->left, targetSum);traverse(root->right, targetSum);//后序遍历位置sum -= root->val;}
};

解法二:分解成子树问题,动态规划思想

遍历到一个节点,继续遍历左孩子和右孩子,且targetSum减去节点的数值。

class Solution {public:// 定义:输入一个根节点,返回该根节点到叶子节点是否存在一条和为 targetSum 的路径bool hasPathSum(TreeNode* root, int targetSum) {if(!root) return false;if(!root->left && !root->right && root->val == targetSum) return true;// 左子树或者右子树有一个满足即可return hasPathSum(root->left, targetSum - root->val) ||hasPathSum(root->right, targetSum - root->val);}
};

113、路径总和ii

给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。

示例:

在这里插入图片描述

输入:root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22
输出:[[5,4,11,2],[5,8,4,5]]

相对于⌈112、路径总和⌋来说,前序和后序位置不仅要维护sum,还要维护路径path

class Solution {
public:vector> res;vector path;int sum = 0;vector> pathSum(TreeNode* root, int targetSum) {traverse(root, targetSum);return res;}void traverse(TreeNode* root, int targetSum){if(!root) return;//前序遍历位置sum += root->val;path.push_back(root->val);if(!root->left && !root->right && sum == targetSum) res.push_back(path);traverse(root->left, targetSum);traverse(root->right, targetSum);//后序遍历位置sum -= root->val;path.pop_back();}
};

437、路径总和iii

给定一个二叉树的根节点 root ,和一个整数 targetSum ,求该二叉树里节点值之和等于 targetSum路径 的数目。

路径 不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。

示例:

在这里插入图片描述


输入:root = [10,5,-3,3,2,null,11,3,-2,null,1], targetSum = 8
输出:3
解释:和等于 8 的路径有 3 条,如图所示

这题及要求你准确理解二叉树的前序后序遍历,还要熟悉前缀和技巧,把前缀和技巧用到二叉树上。

这道题涉及到数组的技巧,暂定先不做。

二叉树是否对称/相等

101、对称二叉树100、相同的树结合起来看,两道题方法和代码上非常相似。

100、相同的树

给你两棵二叉树的根节点 pq ,编写一个函数来检验这两棵树是否相同。

如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。

示例:

img
输入:p = [1,2,3], q = [1,2,3]
输出:true

解法一:分解成子树问题

class Solution {
public:bool isSameTree(TreeNode* p, TreeNode* q) {if(!p || !q) return p == q;if(p->val != q->val) return false;bool left = isSameTree(p->left, q->left);	//比较左子树bool right = isSameTree(p->right, q->right);//比较右子树return left && right;}
};

代码简化为:

class Solution {
public:bool isSameTree(TreeNode* p, TreeNode* q) {// 判断一对节点是否相同if(!p || !q) return p == q;if(p->val != q->val) return false;// 判断其他节点是否相同return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);}
};

解法二:迭代法

class Solution {
public:bool isSameTree(TreeNode* p, TreeNode* q) {queue que;que.push(p);que.push(q);//注意不能加上写成如下代码,否则会报错//if(p) que.push(p);   //if(q) que.push(q);  while (!que.empty()) {  // 接下来就要判断这两颗树是否相等TreeNode* leftNode = que.front(); que.pop();TreeNode* rightNode = que.front(); que.pop();if (!leftNode && !rightNode) {  // 左节点为空、右节点为空,此时说明是相等的continue;}// 左右一个节点不为空,或者都不为空但数值不相同,返回falseif ((!leftNode || !rightNode || (leftNode->val != rightNode->val))) {return false;}que.push(leftNode->left);   que.push(rightNode->left); que.push(leftNode->right);  que.push(rightNode->right); }return true;}
};

注意

que.push(p);
que.push(q);

不能写为:

if(p) que.push(p);   
if(q) que.push(q);  

否则会报错:

执行出错信息:
Line 15: Char 74: runtime error: member access within misaligned address 0xbebebebebebebebe for type 'TreeNode', which requires 8 byte alignment (solution.cpp)
0xbebebebebebebebe: note: pointer points here

SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior prog_joined.cpp:24:74
最后执行的输入:
[]
[0]

加上if判断后,若输入当中有为空的,则无法加入到队列当中,影响后序的代码逻辑运行。

101、对称二叉树

给定一个二叉树,检查它是否是镜像对称的。

101. 对称二叉树

解法一:分解成子树问题

对于二叉树是否对称,要比较的是根节点的左子树与右子树是不是相互翻转的,理解这一点就知道了其实我们要比较的是两颗树(这两颗树是根节点的左右子树),所以在递归遍历的过程中,也是要同时遍历两棵树。

正是因为要遍历两棵树而且要比较内侧和外侧节点,所以准确的来说是一个树的遍历顺序是左右中,一个树的遍历顺序是右左中,只有内侧和外侧节点分别对应相等,这两棵树才是对称的。

返回条件

  • 左节点为空,右节点不为空,不对称,return false;
  • 左不为空,右为空,不对称 return false;
  • 左右都为空,对称,返回true;
  • 左右都不为空,比较节点数值,不相同就return false;
  • 左右节点相等的话,再递归判断子节点;

代码如下:

if (left == NULL && right != NULL) return false;
else if (left != NULL && right == NULL) return false;
else if (left == NULL && right == NULL) return true;
else if (left->val != right->val) return false;
class Solution {
public:bool isSymmetric(TreeNode* root) {if(!root) return true;return compare(root->left, root->right);}//定义:输入左、右节点,返回分别以这两节点为根节点的二叉树是否对称bool compare(TreeNode* left, TreeNode* right){// 注意这里终止条件的代码if(!left || !right) return left == right;if(left->val != right->val) return false;bool outside = compare(left->left, right->right);   //外侧节点比较bool inside = compare(left->right, right->left);    //内侧节点比较return outside && inside;   //左右子节点需要对称相同}
};

代码简化为:

class Solution {
public:bool isSymmetric(TreeNode* root) {if(!root) return true;return compare(root->left, root->right);}//定义:输入左、右节点,返回分别以这两节点为根节点的二叉树是否对称bool compare(TreeNode* left, TreeNode* right){// 该对节点是否对称if(!left || !right) return left == right;if(left->val != right->val) return false;// 其他节点是否对称return compare(left->left, right->right) && compare(left->right, right->left);}
};

解法二:迭代法,不是层序遍历,这里我们可以使用队列来比较两个树(根节点的左右子树)是否相互翻转。

把左右两个子树要比较的元素顺序放进一个容器,然后成对的取出来进行比较,那么其实使用也是可以的。

class Solution {
public:bool isSymmetric(TreeNode* root) {if (root == NULL) return true;queue que;que.push(root->left);   // 将左子树头结点加入队列que.push(root->right);  // 将右子树头结点加入队列while (!que.empty()) {  // 接下来就要判断这两个树是否相互翻转TreeNode* leftNode = que.front(); que.pop();TreeNode* rightNode = que.front(); que.pop();if (!leftNode && !rightNode) {  // 左节点为空、右节点为空,此时说明是对称的continue;}// 左右一个节点不为空,或者都不为空但数值不相同,返回falseif ((!leftNode || !rightNode || (leftNode->val != rightNode->val))) {return false;}que.push(leftNode->left);   // 加入左节点左孩子que.push(rightNode->right); // 加入右节点右孩子que.push(leftNode->right);  // 加入左节点右孩子que.push(rightNode->left);  // 加入右节点左孩子}return true;}
};

572、另一个树的子树

给你两棵二叉树 rootsubRoot ,检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。如果存在,返回 true ;否则,返回 false

二叉树 tree 的一棵子树包括 tree 的某个节点和这个节点的所有后代节点,tree 也可以看做它自身的一棵子树。

示例:

img
输入:root = [3,4,5,1,2], subRoot = [4,1,2]
输出:true

遍历以 root 为根的这棵二叉树的所有节点,用 ⌈100、相同的树⌋ 中的 isSameTree 函数判断以该节点为根的子树是否和以 subRoot 为根的那棵树相同。

class Solution {
public:bool isSubtree(TreeNode* root, TreeNode* subRoot) {if(!root) return root == subRoot;// 判断以 root 为根的二叉树是否和 subRoot 相同if(isSameTree(root, subRoot)) return true;// 去左右子树中判断是否有和 subRoot 相同的子树return isSubtree(root->left, subRoot) || isSubtree(root->right, subRoot);}// 定义:输入两个节点,判断以两个节点为根节点的二叉树是否一样,返回结果bool isSameTree(TreeNode* p, TreeNode* q) {// 判断一对节点是否相同if(!p || !q) return p == q;if(p->val != q->val) return false;// 判断其他节点是否相同return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);}
};

左叶子/左下角问题

404、左叶子之和

给定二叉树的根节点 root ,返回所有左叶子之和。

示例:

img
输入: root = [3,9,20,null,null,15,7] 
输出: 24 
解释: 在这个二叉树中,有两个左叶子,分别是 9 和 15,所以返回 24

首先要注意是判断左叶子,不是二叉树左侧节点,所以不要上来想着层序遍历。

本题遍历二叉树即可,问题是如何判断节点是左叶子呢?

如果左节点不为空,且左节点没有左右孩子,那么这个节点的左节点就是左叶子,必须要通过节点的父节点来判断其左孩子是不是左叶子:

if (node->left != NULL && node->left->left == NULL && node->left->right == NULL) {左叶子节点处理逻辑
}
class Solution {
public:int sum = 0;int sumOfLeftLeaves(TreeNode* root) {traverse(root);return sum;}void traverse(TreeNode* root){if(!root) return;// 找到左侧的叶子节点,记录累加值if(root->left && !root->left->left && !root->left->right){sum += root->left->val;}traverse(root->left);traverse(root->right);}
};

513、找树左下角的值

给定一个二叉树的 根节点 root,请找出该二叉树的 最底层 最左边 节点的值。

假设二叉树中至少有一个节点。

示例:

img
输入: root = [2,1,3]
输出: 1

解法一:递归遍历二叉树

二叉树递归框架代码是先递归左子树,后递归右子树,所以到最大深度时第一次遇到的节点就是左下角的节点

class Solution {
public:int depth;		// 记录 traverse 递归遍历到的深度int maxDepth;	// 记录二叉树的最大深度TreeNode* res;int findBottomLeftValue(TreeNode* root) {traverse(root);return res->val;}void traverse(TreeNode* root){if(!root) return;depth++;// 到最大深度时第一次遇到的节点就是左下角的节点if(depth > maxDepth){maxDepth = depth;res = root;}traverse(root->left);traverse(root->right);depth--;}
};

解法二:层序遍历,很好理解

class Solution {
public:int findBottomLeftValue(TreeNode* root) {queue que;if (root != NULL) que.push(root);int result = 0;	//后续循环会不断刷新result的值while (!que.empty()) {int size = que.size();for (int i = 0; i < size; i++) {TreeNode* node = que.front();que.pop();if (i == 0) result = node->val; // 记录最后一行第一个元素if (node->left) que.push(node->left);if (node->right) que.push(node->right);}}return result;}
};

找树左下角的值会做了,找树右下角的值自然也会做了,也就是把遍历的顺序改变一下:先遍历右子树,再遍历左子树。

完全二叉树

BM35 判断是不是完全二叉树

给定一个二叉树,确定他是否是一个完全二叉树

完全二叉树的定义:若二叉树的深度为 h,除第 h 层外,其它各层的结点数都达到最大个数,第 h 层所有的叶子结点都连续集中在最左边,这就是完全二叉树。(第 h 层可能包含 [1~2h] 个节点)

样例图1:叶子节点出现在最后一层

img

样例图2:叶子节点出现在最后一次和倒数第二层

img

完全二叉树 的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。

本道题的解题关键就是要紧紧抓住完全二叉树的定义,使用层序遍历。如果遇到某个节点为空,进行标记,代表访问到完全二叉树的最下层,若是后续还有访问,则不符合完全二叉树的定义。

注意:

que.push(node->left);
que.push(node->right);

不能写成:

if(node->left) push(node->left);
if(node->right) que.push(node->right);

否则, 完全二叉树最后一层的空节点是访问不到的。

class Solution {public:bool isCompleteTree(TreeNode* root) {//空树一定是完全二叉树if(root == NULL)  return true;queue que;if(root) que.push(root); //定义一个首次出现的标记位bool flag = false; while(!que.empty()){ int size = que.size();for (int i = 0; i < size; i++) {TreeNode* node = que.front();que.pop();//标记第一次遇到空节点if (!node) flag = true; else{//后续访问已经遇到空节点了,说明经过了叶子if (flag) return false;que.push(node->left);que.push(node->right);}}}return true;}
};

222、完全二叉树的节点个数

给你一棵 完全二叉树 的根节点 root ,求出该树的节点个数。

完全二叉树 的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。

示例:

img
输入:root = [1,2,3,4,5,6]
输出:6

首先要搞清楚什么是 ⌈完全二叉树⌋ 和 ⌈满二叉树⌋ :

完全二叉树如下图,每一层都是紧凑靠左排列的,除了最底层节点可能没填满外,其余每层节点数都达到最大值:

img

满二叉树如下图,是一种特殊的完全二叉树,每层都是是满的,像一个稳定的三角形:

img

如果是一个普通二叉树,显然只要向下面这样遍历一边即可,时间复杂度 O(N):

int countNodes(TreeNode* root) {if (root == null) return 0;return 1 + countNodes(root->left) + countNodes(root->right);
}

那如果是一棵二叉树,节点总数就和树的高度呈指数关系:

int countNodes(TreeNode* root) {int h = 0;// 计算树的高度while (root != null) {root = root->left;h++;}// 节点总数就是 2^h - 1return (2 << h) - 1;
}

完全二叉树比普通二叉树特殊,但又没有满二叉树那么特殊,计算它的节点总数,可以说是普通二叉树和完全二叉树的结合版。

完全二叉树只有两种情况,情况一:就是满二叉树,情况二:最后一层叶子节点没有满。

对于情况一,可以直接用 2^树深度 - 1 来计算,注意这里根节点深度为1。

对于情况二,分别递归左孩子,和右孩子,递归到某一深度一定会有左孩子或者右孩子为满二叉树,然后依然可以按照情况1来计算。

image-20220421164919517 image-20220421165226452

可以看出如果整个树不是满二叉树,就递归其左右孩子,直到遇到满二叉树为止,用公式计算这个子树(满二叉树)的节点数量。

class Solution {
public:int countNodes(TreeNode* root) {if (!root) return 0;TreeNode* left = root->left;TreeNode* right = root->right;// 这里初始为0是有目的的,为了下面求指数方便int leftHeight = 0;int rightHeight = 0; // 记录左、右子树的高度while (left) {left = left->left;leftHeight++;}while (right) {right = right->right;rightHeight++;}// 如果左右子树的高度相同,则是一棵满二叉树if (leftHeight == rightHeight) {return (2 << leftHeight) - 1; // 注意(2<<1) 相当于2^2,所以leftHeight初始为0}// 如果左右高度不同,则按照普通二叉树的逻辑计算return countNodes(root->left) + countNodes(root->right) + 1;}
};

这个算法的时间复杂度是 O(logN*logN),但直觉感觉好像最坏情况下是 O(N*logN) ,因为之前的 while 需要 logN 的时间,最后要 O(N) 的时间向左右子树递归:

return 1 + countNodes(root.left) + countNodes(root.right);

关键点在于,这两个递归只有一个会真的递归下去,另一个一定会触发 leftHeight == rightHeight 而立即返回,不会递归下去

所以,算法的递归深度就是树的高度 O(logN),每次递归所花费的时间就是 while 循环,需要 O(logN),所以总体的时间复杂度是 O(logN*logN)。

二叉树展开为链表

114、二叉树展开为链表

给你二叉树的根结点 root ,请你将它展开为一个单链表:

  • 展开后的单链表应该同样使用 TreeNode ,其中 right 子指针指向链表中下一个结点,而左子指针始终为 null
  • 展开后的单链表应该与二叉树 前序遍历 顺序相同。

示例 :

img
输入:root = [1,2,5,3,4,null,6]
输出:[1,null,2,null,3,null,4,null,5,null,6]

解法一:递归遍历

对整棵树进行前序遍历,一边遍历一边构造出一条「链表」。

class Solution {
public:// 虚拟头节点,res->right 就是结果TreeNode* res = new TreeNode(-1);// 用来构建链表的指针TreeNode* p = res;TreeNode* flatten(TreeNode* root) {traverse(root);return res->right;}void traverse(TreeNode* root){if(!root) return;p = new TreeNode(root->val);p = p->right;traverse(root->left);traverse(root->right);}
};

但是注意 flatten 函数的签名,返回类型为 void,也就是说题目希望我们在原地把二叉树拉平成链表。

这样一来,没办法通过简单的二叉树遍历来解决这道题了。

解法二:分解成子树的问题

对于一个节点 x,可以执行以下流程:

1、先利用 flatten(x.left)flatten(x.right)x 的左右子树拉平。

2、将 x 的右子树接到左子树下方,然后将整个左子树作为右子树。

image-20220417194832538

这样,以 x 为根的整棵二叉树就被拉平了,恰好完成了 flatten(x) 的定义。

class Solution {
public://定义:输入一个节点,将以该节点为根节点的二叉树展开成单链表void flatten(TreeNode* root) {if(!root) return;// 利用定义,把左右子树拉平flatten(root->left);flatten(root->right);// 1、左右子树已经被拉平成一条链表TreeNode* left = root->left;TreeNode* right = root->right;// 2、将左子树作为右子树root->left =  nullptr;root->right = left;// 3、将原先的右子树接到当前右子树的末端TreeNode* p = root;// 注意这里的条件是 p->right,而不是 p ,即有右孩子则移动指针while(p->right){p = p->right;}p->right = right;}
};

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...