数据结构——二叉树的修改与构造

  • 一、修改二叉树
    • 226. 翻转二叉树
      • 1.前/后序递归
      • 2.广度优先搜索迭代
      • 3.拓展:修改中序遍历 / 中序统一迭代写法
    • 114. 二叉树展开为链表
  • 二、构造二叉树
    • 106. 从中序与后序遍历序列构造二叉树
      • 递归思路
    • 105. 从前序与中序遍历序列构造二叉树
      • 递归思路
    • 889. 根据前序和后序遍历构造二叉树
      • 思路
    • 654. 最大二叉树
      • 思路
    • 617. 合并二叉树
      • 1.递归
      • 2.迭代

一、修改二叉树

226. 翻转二叉树

226. 翻转二叉树

注意只要把每一个节点的左右孩子翻转一下,就可以达到整体翻转的效果

这道题目使用前序遍历和后序遍历都可以,唯独中序遍历不方便因为中序遍历会把某些节点的左右孩子翻转了两次

使用层序遍历依然是可以的。只要把每一个节点的左右孩子翻转一下的遍历方式都是可以的

1.前/后序递归

class Solution {public TreeNode invertTree(TreeNode root) {if(root==null){return null;}invertTree(root.left);invertTree(root.right);swapTree(root); //前后序都可return root;}public void swapTree(TreeNode root){TreeNode temp = root.left;root.left = root.right;root.right = temp;}
}

2.广度优先搜索迭代

class Solution {public TreeNode invertTree(TreeNode root) {if (root == null) {return null;}ArrayDeque<TreeNode> deque = new ArrayDeque<>();deque.offer(root);while (!deque.isEmpty()) {int size = deque.size();while (size-- > 0) {TreeNode node = deque.poll();swapTree(node);if (node.left != null) {deque.offer(node.left);}if (node.right != null) {deque.offer(node.right);}}}return root;}public void swapTree(TreeNode root) {TreeNode temp = root.left;root.left = root.right;root.right = temp;}
}

3.拓展:修改中序遍历 / 中序统一迭代写法

修改中序遍历,避免节点左右孩子翻转两次的情况

class Solution {public TreeNode invertTree(TreeNode root) {if(root==null){return null;}invertTree(root.left);swapTree(root);invertTree(root.left);return root;}public void swapTree(TreeNode root){TreeNode temp = root.left;root.left = root.right;root.right = temp;}
}

中序统一迭代写法用栈来遍历,而不是靠指针来遍历,避免了递归法中翻转了两次的情况

class Solution {public TreeNode invertTree(TreeNode root) {Deque<TreeNode> stack = new LinkedList<>();if (root != null) {stack.push(root);}while (!stack.isEmpty()) {TreeNode node = stack.peek();if (node!=null) {stack.pop(); if (node.right!=null) stack.push(node.right);  // 右stack.push(node);                // 中stack.push(null); if (node.left!=null) stack.push(node.left);    // 左} else {stack.pop(); node = stack.pop();swapTree(node);}}return root;}public void swapTree(TreeNode root) {TreeNode temp = root.left;root.left = root.right;root.right = temp;}
}

114. 二叉树展开为链表

114. 二叉树展开为链表

class Solution {public void flatten(TreeNode root) {if (root == null) return;//分别处理左右子树flatten(root.left);flatten(root.right);//后序,保证左右指针改变指向不会使孩子丢失TreeNode left = root.left;TreeNode right = root.right;//左指针置为null,右指针指向原左子树root.left = null;root.right = left;//将原本的右子树放在当前右子树的末端TreeNode p = root;while (p.right != null) {p = p.right;}p.right = right;}
}

二、构造二叉树

106. 从中序与后序遍历序列构造二叉树

106. 从中序与后序遍历序列构造二叉树

给定两个整数数组 inorder 和 postorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。

注意: 你可以假设树中没有重复的元素。

例如,给出中序遍历 inorder = [9,3,15,20,7] 后序遍历 postorder = [9,15,7,20,3] 返回如下的二叉树:

递归思路

根据两个顺序构造一个唯一的二叉树,就是以后序数组的最后一个元素为切割点,将中序数组切分,根据中序数组分割情况,反过来在切后序数组。一层一层切下去,每次后序数组最后一个元素就是节点元素。是一种左右子树分治的方法。

  • 第一步:如果数组大小为零的话,说明是空节点了。

  • 第二步:如果不为空,那么取后序数组最后一个元素作为节点元素。

  • 第三步:找到后序数组最后一个元素在中序数组的位置,作为切割点

  • 第四步:切割中序数组,切成中序左数组和中序右数组 (顺序别搞反了,一定是先切中序数组)

  • 第五步:切割后序数组,切成后序左数组和后序右数组

  • 第六步:递归处理左区间和右区间

1、如何切割

中序数组相对比较好切,找到切割点(后序数组的最后一个元素)在中序数组的位置,然后切割。

对于后序数组,首先要舍弃末尾元素,因为这个元素就是中间节点,已经用过了。
后序数组没有明确的切割元素来进行左右切割,但注意中序数组大小一定是和后序数组的大小相同的。中序数组已经切成了左中序数组和右中序数组了,那么后序数组就可以按照左中序数组的大小来切割,切成左后序数组和右后序数组。

中序和后序数组都切割完成后,就可以进行递归了。

2、边界值的计算

注意确定切割的标准,是左闭右开,还有左开右闭,还是左闭右闭,这个就是不变量,要在递归中保持这个不变量。
这里使用左闭右开

左子树-中序数组:中序数组开头 到 找到的节点元素位置
左子树-后序数组:后序数组开头 到 postLeft + (rootIndex - inLeft) (后序数组的起始位置加上左子树长度就是后序数组结束位置了(开区间),左子树的长度 = 根节点索引-左子树)
右子树-中序数组 找到的节点元素位置+1 到 中序数组末尾
右子树-后序数组 postLeft + (rootIndex - inLeft) 到 后序数组倒数第二个元素位置
找到的节点位置rootIndex不进入下次递归

class Solution {public TreeNode buildTree(int[] inorder, int[] postorder) {return buildTree1(inorder, 0, inorder.length, postorder, 0, postorder.length);}public TreeNode buildTree1(int[] inorder, int inLeft, int inRight,int[] postorder, int postLeft, int postRight) {// 没有元素了if (inRight - inLeft < 1) {return null;}// 只有一个元素if (inRight - inLeft == 1) {return new TreeNode(inorder[inLeft]);}// 后序数组postorder里最后一个即为根结点int rootVal = postorder[postRight - 1];TreeNode root = new TreeNode(rootVal);int rootIndex = 0;//记录下标// 根据根结点的值找到该值在中序数组inorder里的位置for (int i = inLeft; i < inRight; i++) {if (inorder[i] == rootVal) {rootIndex = i;break;}}// 根据rootIndex划分左右子树//左中序,左后序root.left = buildTree1(inorder, inLeft, rootIndex,postorder, postLeft, postLeft + (rootIndex - inLeft));//右中序,右后序root.right = buildTree1(inorder, rootIndex + 1, inRight,postorder, postLeft + (rootIndex - inLeft), postRight - 1);return root;}
}

105. 从前序与中序遍历序列构造二叉树

105. 从前序与中序遍历序列构造二叉树

给定两个整数数组 preorder 和 inorder ,其中 preorder 是二叉树的先序遍历, inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。

例如,给出

前序遍历 preorder = [3,9,20,15,7] 中序遍历 inorder = [9,3,15,20,7] 返回如下的二叉树:

递归思路

与106. 从中序与后序遍历序列构造二叉树思路相同,变为以前序数组的第一个元素为切割点,将中序数组切分,根据中序数组分割情况,切前序数组。

class Solution {public TreeNode buildTree(int[] preorder, int[] inorder) {return buildTree2(preorder, 0, preorder.length - 1, inorder, 0, inorder.length - 1);}TreeNode buildTree2(int[] preorder, int preLeft, int preRight,int[] inorder, int inLeft, int inRight) {// 递归终止条件if (inLeft > inRight || preLeft > preRight) return null;// val 为前序遍历第一个的值,也即是根节点的值// idx 为根据根节点的值来找中序遍历的下标int idx = inLeft, val = preorder[preLeft];TreeNode root = new TreeNode(val);for (int i = inLeft; i <= inRight; i++) {if (inorder[i] == val) {idx = i;break;}}// 根据 idx 来递归找左右子树root.left = buildTree2(preorder, preLeft + 1, preLeft + (idx - inLeft),inorder, inLeft, idx - 1);root.right = buildTree2(preorder, preLeft + (idx - inLeft) + 1, preRight,inorder, idx + 1, inRight);return root;}
}

PS:只知道前序和后序遍历序列无法确定唯一的一颗二叉树,因为没有中序遍历就无法确定左右部分,也就无法分割。
如前序遍历是[1 2 3], 后序遍历是[3 2 1],可能有如下四种情况:

889. 根据前序和后序遍历构造二叉树

889. 根据前序和后序遍历构造二叉树

给定两个整数数组,preorder 和 postorder ,其中 preorder 是一个具有 无重复 值的二叉树的前序遍历,postorder 是同一棵树的后序遍历,重构并返回二叉树。

如果存在多个答案,您可以返回其中 任何 一个。

思路

通过前序中序,或者后序中序遍历结果可以确定一棵原始二叉树,但是通过前序后序遍历结果无法确定原始二叉树。

前两道题,可以通过前序或者后序遍历结果找到根节点,然后根据中序遍历结果确定左右子树。这道题,你可以确定根节点,但是无法确切的知道左右子树有哪些节点。

首先把前序遍历结果的第一个元素或者后序遍历结果的最后一个元素确定为根节点的值。
然后把前序遍历结果的第二个元素作为左子树的根节点的值。
在后序遍历结果中寻找左子树根节点的值,从而确定了左子树的索引边界,进而确定右子树的索引边界,递归构造左右子树即可。

class Solution {public TreeNode constructFromPrePost(int[] preorder, int[] postorder) {int len = preorder.length;if (len == 0) return null;TreeNode node = new TreeNode(preorder[0]);if (len == 1) return node;int left = 0;for (int i = 0; i < len; i++) {if (postorder[i] == preorder[1])left = i+1;}// 递归构造左右子树// 根据左子树的根节点索引和元素个数推导左右子树的索引边界node.left = constructFromPrePost(Arrays.copyOfRange(preorder,1,left+1),Arrays.copyOfRange(postorder,0,left));node.right = constructFromPrePost(Arrays.copyOfRange(preorder,left+1,len),Arrays.copyOfRange(postorder,left,len-1));return node;}
}

654. 最大二叉树

654. 最大二叉树

给定一个不含重复元素的整数数组。一个以此数组构建的最大二叉树定义如下:

二叉树的根是数组中的最大元素。
左子树是通过数组中最大值左边部分构造出的最大二叉树。
右子树是通过数组中最大值右边部分构造出的最大二叉树。
通过给定的数组构建最大二叉树,并且输出这个树的根节点。

示例 :

提示:

给定的数组的大小在 [1, 1000] 之间。

思路

代码随想录动图:

构造树一般采用的是前序遍历,因为先构造中间节点,然后递归构造左子树和右子树。

  • 递归函数的参数和返回值:参数就是传入的是存放元素的数组,返回该数组构造的二叉树的头结点
  • 终止条件:当递归遍历的时候,如果传入的数组大小为1,说明遍历到了叶子节点了。那么应该定义一个新的节点,并把这个数组的数值赋给新的节点,然后返回这个节点。 这表示一个数组大小是1的时候,构造了一个新的节点,并返回。
  • 单层递归的逻辑:
  1. 先要找到数组中最大的值和对应的下标, 最大的值构造根节点,下标用来下一步分割数组。
  2. 最大值所在的下标左区间 构造左子树
    如果不让空节点(空指针)进入递归,就要加判断maxValueIndex > 0,因为要保证左区间至少有一个数值。
  3. 最大值所在的下标右区间 构造右子树
    如果不让空节点(空指针)进入递归,要加判断maxValueIndex < (nums.size() - 1),确保右区间至少有一个数值。
class Solution {public TreeNode constructMaximumBinaryTree(int[] nums) {return constructMaximumBinaryTree1(nums, 0, nums.length);}public TreeNode constructMaximumBinaryTree1(int[] nums, int leftIndex, int rightIndex) {if (rightIndex - leftIndex < 1) {// 没有元素了return null;}if (rightIndex - leftIndex == 1) {// 只有一个元素return new TreeNode(nums[leftIndex]);}int maxIndex = leftIndex;// 最大值所在位置int maxVal = nums[maxIndex];// 最大值for (int i = leftIndex + 1; i < rightIndex; i++) {if (nums[i] > maxVal){maxVal = nums[i];maxIndex = i;}}TreeNode root = new TreeNode(maxVal);// 根据maxIndex划分左右子树root.left = constructMaximumBinaryTree1(nums, leftIndex, maxIndex);root.right = constructMaximumBinaryTree1(nums, maxIndex + 1, rightIndex);return root;}
}

617. 合并二叉树

617. 合并二叉树

给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠。

你需要将他们合并为一个新的二叉树。合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 NULL 的节点将直接作为新二叉树的节点。

注意: 合并必须从两个树的根节点开始。

示例 1:

1.递归

合并需要同时遍历两个二叉树,和遍历一个树逻辑是一样的,只不过传入两个树的节点,同时操作。

本题前中后序遍历都可以使用

  • 递归函数的参数和返回值:要传入两个二叉树的根节点,返回值就是合并之后二叉树的根节点。
  • 终止条件:传入了两个树,就有两个树遍历的节点t1 和 t2,如果t1 == NULL 了,两个树合并就是 t2 了(如果t2也为NULL也无所谓,合并之后就是NULL);反过来如果t2 == NULL,那么两个数合并就是t1。
  • 单层递归的逻辑:重复利用t1树,将t1作为合并之后树的根节点。单层递归中,把两棵树的元素加到一起。接下来t1的左子树是合并t1左子树t2左子树之后的左子树;t1的右子树是合并t1右子树t2右子树之后的右子树。
    也可以重新定义新的树用于存储结果。

前序:

class Solution {public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {if (root1==null) return root2;// 如果t1为空,合并之后就是t2if (root2==null) return root1;// 如果t2为空,合并之后就应该是t1root1.val+=root2.val; // 中root1.left = mergeTrees(root1.left,root2.left); // 左root1.right = mergeTrees(root1.right,root2.right); // 右return root1; }
}

中序:

class Solution {public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {if (root1==null) return root2;// 如果t1为空,合并之后就是t2if (root2==null) return root1;// 如果t2为空,合并之后就应该是t1root1.left = mergeTrees(root1.left,root2.left); // 左root1.val+=root2.val; // 中root1.right = mergeTrees(root1.right,root2.right); // 右return root1; }
}

后序:

class Solution {public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {if (root1==null) return root2;// 如果t1为空,合并之后就是t2if (root2==null) return root1;// 如果t2为空,合并之后就应该是t1root1.left = mergeTrees(root1.left,root2.left); // 左root1.right = mergeTrees(root1.right,root2.right); // 右root1.val+=root2.val; // 中return root1; }
}

2.迭代

使用栈模拟层序遍历:

class Solution {public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {if (root1==null) return root2;// 如果t1为空,合并之后就是t2if (root2==null) return root1;// 如果t2为空,合并之后就应该是t1Stack<TreeNode> stack = new Stack<>();stack.push(root2);stack.push(root1);while (!stack.isEmpty()) {TreeNode node1 = stack.pop();TreeNode node2 = stack.pop();node1.val += node2.val;if (node2.right != null && node1.right != null) {stack.push(node2.right);stack.push(node1.right);} else {if (node1.right == null) {node1.right = node2.right;}}if (node2.left != null && node1.left != null) {stack.push(node2.left);stack.push(node1.left);} else {if (node1.left == null) {node1.left = node2.left;}}}return root1;}
}

使用队列模拟层序遍历:

class Solution {public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {if (root1==null) return root2;// 如果t1为空,合并之后就是t2if (root2==null) return root1;// 如果t2为空,合并之后就应该是t1Queue<TreeNode> queue = new LinkedList<>();queue.offer(root1);queue.offer(root2);while (!queue.isEmpty()) {TreeNode node1 = queue.poll();TreeNode node2 = queue.poll();// 此时两个节点一定不为空,val相加node1.val = node1.val + node2.val;// 如果两棵树左节点都不为空,加入队列if (node1.left != null && node2.left != null) {queue.offer(node1.left);queue.offer(node2.left);}// 如果两棵树右节点都不为空,加入队列if (node1.right != null && node2.right != null) {queue.offer(node1.right);queue.offer(node2.right);}// 若node1的左节点为空,直接赋值if (node1.left == null && node2.left != null) {node1.left = node2.left;}// 若node2的左节点为空,直接赋值if (node1.right == null && node2.right != null) {node1.right = node2.right;}}return root1;}
}

数据结构——二叉树的修改与构造相关推荐

  1. 数据结构-二叉树[递归实现](构造,析构,先序遍历,中序遍历,后续遍历,层次遍历)

    数据结构-二叉树[递归实现] 一.二叉树概念 1.定义 二叉树(Binary Tree)是n(n不小于0)个节点组成的有限集合,且满足以下条件之一 (1)n=0时,为空二叉树(无节点) (2)n> ...

  2. 3. 数据结构--二叉树 BST AVL树 Huffman

    数据结构–二叉树 KEY:(不敢相信没有堆-) 二叉树的定义及其主要特征 ☑️ 二叉树的顺序存储结构和链式存储结构实现 二叉树的遍历及应用 二叉排序(查找.检索)树 (BST) 平衡的二叉检索树- A ...

  3. 数据结构----二叉树叶子结点到根节点的高度计算

    数据结构----二叉树叶子结点到根节点的高度计算 代码: #include<stdio.h> #include<stdlib.h> typedef struct bstTree ...

  4. 二叉树----数据结构:二叉树的三种遍历及习题

    二叉树----数据结构:二叉树的三种遍历,利用递归算法. 关于二叉树的遍历,应用非常广泛,不单单是访问打印结点,还可以进行一系列的操作,如赋值.删除.查找.求二叉树的深度等等. 有递归和非递归两种算法 ...

  5. 数据结构-二叉树入门Go语言实现

    数据结构-二叉树入门Go语言实现 之前我们一直在谈的是一对一的线性结构,可现实中,还有很多一对多的情况需要处理,所以我们需要研究这种一对多的数据结构--"树",考虑它的各种特性,来 ...

  6. 数据结构——二叉树总结

    数据结构-二叉树总结 写在前面 二叉树遍历 递归实现先.中.后序遍历 非递归遍历 先序非递归 中序非递归 后序非递归 层次遍历 二叉树还原 先序中序建树 后序中序建树 层次中序建树 二叉树应用 二叉查 ...

  7. 数据结构——二叉树遍历原理及方法

    数据结构--二叉树遍历原理及方法 二叉树的遍历(traversing binary tree)是指从根结点出发,按照某种次序依次访问二叉树中所有结点,使得每个结点被访问一次且仅被访问一次. 创建二叉树 ...

  8. 数据结构 -- 二叉树

          这篇文章介绍的是经典的数据结构--二叉树,在这篇文章里介绍了几乎二叉树的所有操作.       二叉树给我们最重要的印象莫过于递归,因为这棵树就是递归的,所以,我在解决各个问题时大部分都用 ...

  9. 数据结构 - 二叉树 - 面试中常见的二叉树算法题

    数据结构 - 二叉树 - 面试中常见的二叉树算法题 数据结构是面试中必定考查的知识点,面试者需要掌握几种经典的数据结构:线性表(数组.链表).栈与队列.树(二叉树.二叉查找树.平衡二叉树.红黑树).图 ...

最新文章

  1. 如何理解深度学习分布式训练中的large batch size与learning rate的关系?
  2. 第16讲:异步爬虫的原理和解析
  3. Linux基础-查看文件与目录
  4. 亚伦•斯沃茨:提升时间的品质
  5. 一个使用numpy.ones()的矩阵| 使用Python的线性代数
  6. vue项目发布时去除console语句
  7. Debug JDK源码没变量值怎么办?
  8. c语言成绩管理系统1.0,c语言成绩管理系统完整附源码v1.0 免费版
  9. 激光雕刻机DIY之二:GRBL的下载与参数配置
  10. python英文分词统计词频_Python 分词并统计词频
  11. 基于C++和QT实现的房贷计算器设计
  12. keyshot怎么贴logo_KeyShot实例渲染技巧教程,教你如何给产品添加有织纹的Logo
  13. CMake 编译时出现错误 coffe转换到 COFF 期间失败: 文件无效或损坏
  14. excel的sumif()函数和sumifs()函数
  15. 怎么恢复我在计算机里删掉的文档,如题,如何彻底删除电脑中的文件,使文件不能恢复?(我的方式是直接? 爱问知识人...
  16. mysql截取字符串后缀_Mysql字符串截取函数SUBSTRING的用法说明
  17. 20140228老沙的感觉
  18. 为 UOS 浏览器增加屏蔽广告功能
  19. Error: Request failed with status code 403
  20. 区块链-什么是闪电网络?

热门文章

  1. Ethereum HD KDF
  2. 陪读者们打响第一枪!
  3. R语言绘图基础篇-PCA加置信圈
  4. 团战可以输、提莫必须死
  5. 应聘时被问到「你的期望薪资是多少」,怎样回答才是最合理的呢?
  6. php中 prefix,linux中--prefix命令是什么意思?
  7. 如何在程序中创建快捷方式
  8. linux 压缩文件解压到到指定的目录
  9. 周年纪念日的自作视频
  10. 实例021:猴子偷桃 猴子吃桃问题:猴子第一天摘下若干个桃子,当即吃了一半,还不瘾,又多吃了一个第二天早上又将剩下的桃子吃掉一半,又多吃了一个。以后每天早上都吃了前一天剩下的一半零一个。到第10天早上