二叉树的所有路径
给你一个二叉树的根节点 root ,按 任意顺序 ,返回所有从根节点到叶子节点的路径。
叶子节点 是指没有子节点的节点。
来源:https://leetcode-cn.com/problems/binary-tree-paths/
这里主要考察的是二叉树的遍历。
通过这个题目,我们可以运用二叉树的前序遍历来解决这个问题,每遍历到一个节点,就将这个节点添加到路径path中,如果这个节点是一个叶子节点,那么此时的路径表示的是根节点到叶子节点的路径,所以我们将这个路径添加到paths中,否则,如果这个节点不是一个叶子节点,那么需要将这个节点从path中删除。当前序遍历完毕之后,就会获得所有的从根节点到叶子节点的路径。
class Solution {
/*
利用前序遍历:
首先需要将节点的值添加到subList中,然后遍历它的左子树,如果当前
这个节点是一个叶子节点,所以需要将这个集合添加到list中,然后将
从subList中删除这个叶子节点,返回到上一次递归中
*/
List<String> paths = new ArrayList<String>();
List<Integer> path = new ArrayList<Integer>();
public List<String> binaryTreePaths(TreeNode root) {
getPaths(root);
return paths;
}
public void getPaths(TreeNode root){
if(root == null)
return;
path.add(root.val);
if(root.left == null && root.right == null){
//如果当前节点是一个叶子节点,那么需要将这个subList数字构成的字符串添加到list中,然后将从subList中删除最后一个节点
StringBuilder stringBuilder = new StringBuilder();
for(int num: path){
stringBuilder.append(num + "->");
}
String str = stringBuilder.toString();
paths.add(str.substring(0,str.length() - 2));
path.remove(path.size() - 1);
return;
}
getPaths(root.left);
getPaths(root.right);
path.remove(path.size() - 1);//当所有的子节点都遍历了,需要将这个节点从路径path中删除
}
}
然而这个算法我们是可以进行优化的,从path的类型中入手,我们可以将path转成一个StringBuilder类型,这样当遍历到的节点是一个叶子节点的时候,我们不需要遍历path的值进行字符串的拼接了,然而这时候有一个问题,就是我们将一个节点的所有子节点都遍历了,如何将这个节点从路径中删除呢?这个操作相当于上面的path.remove(path.size() - 1)。尽管path(StirngBuilder类型的)对象存在这个deleteCharAt()方法,并且要删除的节点必然是path最后一个,所以可以通过path.deleteCharAt(path.length() - 1)即可,但是还会有可能多出来的"->",所以可以通过3个deleteCharAt(path.length() - 1)吗?即对应的代码如下面所示:
public void getPaths(TreeNode root){
if(root == null)
return;
path.append(root.val);
if(root.left == null && root.right == null){
paths.add(path.toString());
path.deleteCharAt(path.length() - 1);//删除路径中最后一个节点
// System.out.println("root.val = " + root.val + ", path = " + path.toString());
return;
}
path.append("->");
getPaths(root.left);
getPaths(root.right);
path.deleteCharAt(path.length() - 1);
path.deleteCharAt(path.length() - 1);
path.deleteCharAt(path.length() - 1);//将左右子节点都遍历之后,需要将当前这个节点从path中删除,但是因为存在->,所以前面两个deleteCharAt时用来删除->的,而最后一个才是删除这个节点的
// System.out.println("root.val = " + root.val + ", path = " + path.toString());
}
看起来上面的程序没有什么问题,然而如果这个节点的值不只是一位的数字,甚至的如果存在负数,这时候就会出现问题了,可以通过上面两个System.out.println进行检验:
那么应该怎样完善呢?这时候需要在方法中传递的参数进行完善,将path传递到方法中,这时候就可以保证完美解决上面的情况:
class Solution {
List<String> paths = new ArrayList<String>();
StringBuilder path = new StringBuilder();
public List<String> binaryTreePaths(TreeNode root) {
getPaths(root,path);
//getPaths(root);
return paths;
}
public void getPaths(TreeNode root,StringBuilder path){
if(root == null)
return;
path.append(root.val);
if(root.left == null && root.right == null){
paths.add(path.toString());
// System.out.println("root.val = " + root.val + ", path = " + path.toString());
return;
}
path.append("->");
getPaths(root.left,new StringBuilder(path));//通过新建一个StringBuilder,并且初始值是当前的path,那么
getPaths(root.right,new StringBuilder(path));
// System.out.println("root.val = " + root.val + ", path = " + path.toString());
}
}
验证二叉搜索树
给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。
有效 二叉搜索树定义如下:
节点的左子树只包含 小于 当前节点的数。
节点的右子树只包含 大于 当前节点的数。
所有左子树和右子树自身必须也是二叉搜索树。
来源:https://leetcode-cn.com/problems/validate-binary-search-tree/submissions/
方法一:中序遍历
我们知道二叉搜索树中序遍历得到的序列是一个升序的序列,所以利用这个特性,我们就可以进行判断了:
每遍历到一个节点,那么我们就将这个节点和中序序列的前一个值min进行比较,如果min大于等于当前节点的值,说明中序序列并不是升序的,说明不是一个二叉搜索树,直接返回false,否则如果min小于当前节点的值,此时需要更新min为root.val,然后继续进行二叉搜索树的中序遍历。
对应代码:
class Solution {
/*
二叉搜索树中序遍历得到的序列是一个升序序列,所以如果下表为i的值,即nums[i]必然大于nums[i - 1],
所以这里的nums[i - 1]就相当于min,而nums[i]就是root.val,如果root.val小于等于min,说明中序
序列不是一个升序数组,所以不是二叉搜索树,直接返回false,否则如果root.val大于min,这时候
需要更新min 为 root.val。重复上述操作
*/
int min = Integer.MIN_VALUE;
boolean flag = false;//用于第一次找到二叉搜索树的最小值
public boolean isValidBST(TreeNode root) {
if(root == null)
return true;
boolean left = isValidBST(root.left);//遍历左子树
/*
不可以是为了不定义flag,然后直接这样写
if(min != Integer.MIN_VALUE && min >= root.val)
return false;
虽然flag的作用仅仅是为了第一次初始化最小值,但是
如果不定义flag,然后利用上面的代码的话,就会出现错误
例如[Integer.MIN_VALUE,Integer.MIN_VALUE],此时利用上面的代码
就会出现错误
*/
if(flag && min >= root.val || !left)//如果第一次已经更新了min,这时候min并不是Integer.MIN_VALUE了,而是左子树的最小值,此时需要进行root.val与min的比较了
return false;
min = root.val;//更新min
flag = true;//表示更新成功了最小值
boolean right = isValidBST(root.right);
return right;
}
}
方法二:分治法
将整棵树分成左右子树进行判断,然后需要将两者的值进行与运算的值返回即可。
所以我们应该怎样进行呢?我们都知道一棵二叉搜素树的根节点的值root.val必然是在[left_min,right_max]范围,并且左子树的所有节点的值都小于root.val,右子树的所有节点的值都大于root.val,所以基于这些特征,我们将可以调用方法isValid(root,low,upper),判断左子树的时候,因为
左子树的所有节点的值都小于根节点的值,所以upper一直是root.val,而low则更新为null即可,表示负无穷小,判断右子树的时候,因为右子树的所有节点的值都大于根节点的值,所以low = root.val,而将upper赋值为null,表示正无穷。此时需要保证节点的值都在[low,upper]范围才可以说是一个有效的二叉搜索树,否则不是.
class Solution {
public boolean isValidBST(TreeNode root) {
return isValidBST(root,null,null);//方法的重载
}
public boolean isValidBST(TreeNode root,Integer low,Integer upper){
if(root == null)
return true;//如果节点为null,直接返回true
//如果当前节点的值不在[low,upper]范围直接返回false,这样就不需要进行递归了
if(low != null && low >= root.val)
return false;
if(upper != null && upper <= root.val)
return false;
//当前节点在[low,upper]范围,这时候需要进入递归,判断左右子树是否符合要求
boolean left = isValidBST(root.left,low,root.val);
if(!left)
return false;//如果左子树不符合要求,直接返回false,避免在left为false的情况下进入判断右子树
boolean right = isValidBST(root.right,root.val,upper);
return right;
}
}
二叉树的最大深度
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。
来源:https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/
分别获取当前节点的左右子树的深度left、right,这时候当前节点的最大深度就是Math.max(left,right) + 1。
对应代码:
class Solution {
public int maxDepth(TreeNode root) {
if(root != null){
int left,right,max;
left = maxDepth(root.left);
right = maxDepth(root.right);//分别统计当前节点左右子数的深度
max = left > right ? left : right;
return max + 1; //max + 1就是当前这个节点的深度
}else{
return 0;
}
}
}
平衡二叉树
给定一个二叉树,判断它是否是高度平衡的二叉树。
本题中,一棵高度平衡二叉树定义为:
一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。
来源:https://leetcode-cn.com/problems/balanced-binary-tree/
因为左右两个子树的高度差的绝对值不超过1,所以这时候我们只要获取左右子树的高度(也即深度),然后判断两者的差的绝对值是否大于1,如果是,说明不是平衡二叉树,直接返回-1,表示不是平衡二叉树,否则是,那么就将返回当前节点的高度。
对应代码:
class Solution {
public boolean isBalanced(TreeNode root) {
return judge(root) != -1;
}
public int judge(TreeNode root){
if(root == null)
return 0;
int left,right,max;
left = judge(root.left);//统计左子树的高度
if(left == -1)//如果left等于-1,说明当前节点的左子树中不符合平衡二叉树要求,直接返回-1
return -1;
right = judge(root.right);//统计右子树的高度
if(right == -1)//如果right等于-1,说明当前节点的右子树不符合平衡二叉树要求,直接返回-1(这两步是判断left\right是否等于-1十分有必要的,下面有讲到为什么必要)
return -1;
/*
判断两个子树的高度差的绝对值是否大于1,如果是,说明不是平衡二叉树,直接返回-1,否则说明当前
节点的左右子树符合平衡二叉树的要求,但是还没有办法知道包括当前节点的子树是否符合要求,所以
需要往上递归,返回包括当前节点在内的子树的高度
*/
return Math.abs(left - right) > 1 ? -1 : Math.max(left,right) + 1;
}
}
为什么left、right判断是否等于-1是必要的呢?请看下面的图解:
二叉树的最大路径和
路径 被定义为一条从树中任意节点出发,沿父节点-子节点连接,达到任意节点的序列。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点,且不一定经过根节点。
路径和 是路径中各节点值的总和。
给你一个二叉树的根节点 root ,返回其 最大路径和 。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/binary-tree-maximum-path-sum
因为这里的路径定义为一条从树中任意节点触发,沿父节点-子节点连接,到达任意节点的序列。所以一个当前节点的路径序列可能有三种情况:
①左右子节点都小于0,所以当前节点在内的路径和就是自身节点的值
②左右子节点中都大于0,所以包含当前节点在内的路径和就是自身结点的值加上左右子节的值,即root.val + left + right
③否则两者中含有一个大于0,那么包含当前节点在内的路径和就是自身节点的值加上left\right中大于0的那个值
所以在知道左右子树的路径和left\right,那么我们就可以根据上面三种情况进行求解了,所以可以利用二叉树的后序遍历来实现:
class Solution {
int res = -9999;
public int maxPathSum(TreeNode root) {
if(root == null)
return 0;
findMax(root);
return res;
}
public int findMax(TreeNode root){
if(root != null){
//如果当前的节点不为空,那么就开始进入递归,获取左右子数的路径
int left = findMax(root.left);
int right = findMax(root.right);
int max = left > right ? left : right;
int route = root.val;
if(left > 0)//如果左子树的值大于0,说明根节点可以和左子树连成路径
route += left;
if(right > 0)//如果右子节点的值大于0,说明根节点可以和它的右子树连成一个路径
route += right;
if(route > res) //获取这个节点形成的路径的值,从而获取整棵树中的最长路径
res = route;
/*
注意的是,这里返回的并不是route,因为route有可能是当前父节
点和他的左右子树都相连了,此时返回到上一个节点的话,那么就
存在了两条路径了,明显不合理,所以返回的不是route,而是父
节点或者父节点和他的一个子节点
所以这个返回值表示的是(当前节点)或者(当前节点 + 左右子树中最大的路径)的路径和
*/
return max > 0 ? root.val + max : root.val;
}else{
return 0;
}
}
}
如果本题没有讲清楚,请看我的这篇博客:https://blog.csdn.net/weixin_46544385/article/details/117716698
二叉树的层序遍历II
给定一个二叉树,返回其节点值自底向上的层序遍历。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/binary-tree-level-order-traversal-ii
基于题目要求,我们可以实现从上往下,逐层遍历,将对应地结果存放到list中,此时list的倒序就是我们想要的,可以调用Collections.reverse(list)即可.
当然如果我们要求不可以调用Collections的reverse方法应该怎么办呢?那么需要将每一层元素放到ans的开头,即执行ans.add(0,sub)即可,或者定义一个双端队列,每获得一层元素,就将这一层元素放到双端队列的开头,然后二叉树遍历完毕之后,将遍历双端队列,将元素添加到ans即可。
利用双端队列:
class Solution {
public List<List<Integer>> levelOrderBottom(TreeNode root) {
/*
利用队列的层序遍历,只是在上面的基础上,将ans的元素倒序即可
利用双端队列
*/
List<List<Integer>> ans = new ArrayList<List<Integer>>();
if(root == null)
return ans;
Deque<List<Integer>> deque = new LinkedList<List<Integer>>();//双端队列,此时从头到尾就是自底向上的每一层元素
Queue<TreeNode> queue = new LinkedList<TreeNode>();
queue.offer(root);
int depth = 1;
while(!queue.isEmpty()){
int size = queue.size();
List<Integer> sub = new ArrayList<Integer>();
for(int i = 0; i < size; ++i){
root = queue.poll();
sub.add(root.val);
if(root.left != null)
queue.offer(root.left);
if(root.right != null)
queue.offer(root.right);
}
deque.offerFirst(sub);//将每一层元素都添加到双端队列的开头
++depth;
}
//遍历从头开始遍历双端队列,将元素添加到ans即可
while(!deque.isEmpty()){
ans.add(deque.pollFirst());
}
return ans;
}
}
利用Collections.reverse方法:
public List<List<Integer>> levelOrderBottom(TreeNode root) {
List<List<Integer>> ans = new ArrayList<List<Integer>>();
if(root == null)
return ans;
Queue<TreeNode> queue = new LinkedList<TreeNode>();
queue.offer(root);
int depth = 1;
while(!queue.isEmpty()){
int size = queue.size();
List<Integer> sub = new ArrayList<Integer>();
for(int i = 0; i < size; ++i){
root = queue.poll();
sub.add(root.val);
if(root.left != null)
queue.offer(root.left);
if(root.right != null)
queue.offer(root.right);
}
ans.add(sub);
/*
将这一层元素添加到ans的开头,从而实现倒序,这时候不需再最后调用Collections.reverse(ans)
方法了,直接返回ans即可
ans.add(0,sub);
*/
++depth;
}
Collections.reverse(ans);//将ans变成他的倒序返回
return ans;
}
填充每个节点的下一个右侧节点指针
给定一个 完美二叉树 ,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下:
struct Node {
int val;
Node *left;
Node *right;
Node *next;
}
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。
初始状态下,所有 next 指针都被设置为 NULL。
进阶:
你只能使用常量级额外空间。
使用递归解题也符合要求,本题中递归程序占用的栈空间不算做额外的空间复杂度。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/populating-next-right-pointers-in-each-node
方法一:层序遍历
利用层序遍历的时候,需要利用队列这个数据结构,每次从队列中跳出一个节点,就需要将这个跳出队列的节点root的next指针域指向队列头这个节点,然后root这个节点的子节点压入到队列中。
对应的代码:
class Solution {
public Node connect(Node root) {
if(root == null)
return null;
Queue<Node> queue = new LinkedList<Node>();
queue.offer(root);
Node tmp;
int i,size,depth = 1;
while(!queue.isEmpty()){
size = queue.size();//第depth层节点
for(i = 0; i < size; ++i){
tmp = queue.poll();
if(i != size - 1){
/*
因为当前这个节点不是第depth层的最后一个节点,所以
可以将从队列中跳出的节点tmp的next指针域指向队列头的节点
由于每个节点的next初始值为NULL,所以如果是第depth层最后一
个节点,不需要进行任何操作
*/
tmp.next = queue.peek();
}
if(tmp.left != null)
queue.offer(tmp.left);
if(tmp.right != null)
queue.offer(tmp.right);
}
++depth;
}
return root;
}
}
方法二:
由于在完成第N层的时候,这时候第N层的next连接得到的就类似于一个链表,并且链表的顺序和队列的出队顺序是一样的,所以通过遍历第N层来完成第N + 1层的next指针指向。
同时由于这棵树是一颗完美二叉树,所以除了最后一层外,其余层都含有2 ^ (depth - 1)个节点,例如第一层含有2 ^ (1 - 1) = 1个节点,因此需要判断是否已经将整个程序执行完毕,只需要判断当前这一层的第一个节点的左子节点是否为null,如果是,说明当前这一层是最后一层,此时已经将程序执行完毕了,否则,如果不为null,说明当前这一层不是最后一层,还需要继续循环。
同时因为是一颗完美二叉树,所以在当前这一层的第一个节点的左子节点不为null的情况下,只需要将node.left.next = node.right;node.right.next = node.next.left;但是执行node.right.next时需要进行判断node.next是否为NULL,否则就会发生报错。
对应代码:
class Solution {
/*
public Node connect(Node root) {
if(root == null)
return null;
Queue<Node> queue = new LinkedList<Node>();
queue.offer(root);
Node tmp;
int i,size;
while(!queue.isEmpty()){
size = queue.size();
for(i = 0; i < size; ++i){
tmp = queue.poll();
if(i == size - 1){
tmp.next = null;
}else{
tmp.next = queue.peek();
}
if(tmp.left != null)
queue.offer(tmp.left);
if(tmp.right != null)
queue.offer(tmp.right);
}
}
return root;
}
*/
public Node connect(Node root) {
if(root == null)
return null;
Node leftMost = root;
int depth = 1;
while(leftMost.left != null){
//如果leftMost.left不是null,说明leftMost不是最后一层的第一个节点
Node head = leftMost;
/*
遍历第depth层的节点,从而将第depth + 1层的节点实现next指向
所以当depth是倒数第二层的时候,已经将最后一层的节点实现了next
指向,当depth是最后一层的时候,此时leftMost是最后一层的第一个
节点,这时候leftMost.left为null,可以退出循环了
*/
while(head != null){
if(head.right != null){
//如果最后一层只有一个节点,那么head.right为null,所以需要避免这一种情况
head.left.next = head.right;
if(head.next != null)//如果当前head是这一层的最后一个节点,那么head.next为null
head.right.next = head.next.left;
}
head = head.next;
}
leftMost = leftMost.left;//第depth + 1层的第一个节点,如果为null,表示depth是最后一层
++depth;
}
return root;
}
}
填充每个节点的下一个右侧节点指针II
给定一个二叉树
struct Node {
int val;
Node *left;
Node *right;
Node *next;
}
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。
初始状态下,所有 next 指针都被设置为 NULL。
进阶:
你只能使用常量级额外空间。
使用递归解题也符合要求,本题中递归程序占用的栈空间不算做额外的空间复杂度。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/populating-next-right-pointers-in-each-node-ii
本题和上面的区别就在于:本题的二叉树并不是完美二叉树,也就是说每一层的节点的数目在[1,2 ^ (depth - 1)]范围,所以并不像上面一题那样,只有最后一层节点数目在[1,2^(depth - 1)],其余层的节点数目都是2 ^(depth - 1).
所以可能会有这样的情况:
所以基于这种情况,我们不能向上面一题一样,第一个while循环条件是leftMost.left != null。而是根据当前第depth层的第一个节点子节点的情况以及leftMost.next是否为null,从而进行对应得操作。
-
当前节点leftMost的左子节点不为null,这时候可以进行相应的操作,而leftMost的左子节点将可能作为下一层的第一个节点进行操作.例如上面的情况1,2,4,5(将根节点作为leftMost的时候)
-
当前节点leftMost的左子节点为null,但是右子节点不为null,这时候可以进行相应的操作,而leftMost的右子节点将可能作为下一层的第一个节点进行操作.例如上面的以根节点为leftMost的情况3,以第二层的第一个节点为leftMost的情况5.
-
当前节点leftMost是叶子节点,表明了不可以将这个叶子节点作为leftMost,所以需要更新leftMost为leftMost.next。例如上面以第二层的第一个节点作为leftMost的情况2.
对应代码:
class Solution {
/*
由于不是一颗完美二叉树,所以不可以通过上一层的第一个节点的左子节点
是否为null,从而判断上一层是否为最后一层。那么应该怎样依据上一层来进行
下一层的next指向呢?道理和完美二叉树是一样的,只是第一个while循环的条件
判断不是leftMost.left != null,而应该是leftMost.left != null || leftMost.right != null || leftMost.next != null
*/
public Node connect(Node root) {
if(root == null)
return root;
Node leftMost = root,tmp,head;
int depth = 1;
while(leftMost.left != null || leftMost.right != null || leftMost.next != null){
/*
因为不是一颗完美二叉树,所以每一层的节点数目是[1,2^(depth - 1)]个
因此当利用第depth层来实现第depth + 1层的next指向的时候,
不应该是判断第depth层的第一个节点的左子节点是否为null,
而应该考虑第depth层的第一个节点是否为叶子节点,如果是,执行leftMost =
leftMost.next,否则就选择这个节点的子节点作为第depth + 1层的第一个节点
*/
if(leftMost.left != null){
/*
如果第depth层的第一个节点的左子节点不为空,那么将从这个节点开始执行
next指向,然后完成了depth + 1层的节点的next指向的时候,
更新leftMost为leftMost.left
*/
help(leftMost);
leftMost = leftMost.left;
++depth;
}else if(leftMost.right != null){
/*
如果第depth层的第一个节点的右子节点不为空,那么将从这个节点开始执行
next指向,然后完成了depth + 1层的节点的next指向的时候,
更新leftMost为leftMost.right
*/
help(leftMost);
leftMost = leftMost.right;
++depth;
}else{
/*
如果是一个叶子节点,则需要执行leftMost = leftMost.next
*/
leftMost = leftMost.next;
}
}
return root;
}
public void help(Node head){
/*
每次执行的时候,需要考虑这个节点是否为叶子节点,只含有一个子节点
两个子节点的情况
1、两个子节点时:
直接执行head.left.next = head.right;
但是执行head.right.next的时候,需要找到head后面含有子节点的第一个节点tmp,
才可以执行head.right.next = tmp的第一个子节点
2、只有一个左子节点的时候:
执行head.left.next的时候,需要找到head后面含有子节点的第一个节点tmp,
才可以执行head.left.next = tmp的第一个子节点
3、只有一个右子节点的时候:
道理同2
4、不管是否为叶子节点,在进行上面的操作之后,同样需要执行head = head.next
知道将第depth层的节点遍历完
*/
Node tmp;
while(head != null){
if(head.left != null){
//当前这个节点的左子节点不为null
if(head.right != null) //如果head.right不为null,那么直接执行head.left.next = head.right
head.left.next = head.right;
else{
//否则,如果head.right为null,相当于情况2,这时候需要获取head后面第一
//含有子节点的tmp,然后将head.left.next = tmp的第一个子节点
help(head.left,head);
head = head.next;
continue;
}
}
if(head.right != null){
//如果当前节点的右子节点不为null,那么需要找到head后面的第一个子节点不为
//null的节点tmp,然后执行head.right.next = tmp的第一个子节点
help(head.right,head);
}
head = head.next;
}
}
public void help(Node head,Node tmp){
while(tmp.next != null){
if(tmp.next.left != null){
head.next = tmp.next.left;
break;
}else if(tmp.next.right != null){
head.next = tmp.next.right;
break;
}
tmp = tmp.next;
}
}
}
二叉树展开为链表
给你二叉树的根结点 root ,请你将它展开为一个单链表:
展开后的单链表应该同样使用 TreeNode ,其中 right 子指针指向链表中下一个结点,而左子指针始终为 null 。
展开后的单链表应该与二叉树 先序遍历 顺序相同。
示例 1:
输入:root = [1,2,5,3,4,null,6]
输出:[1,null,2,null,3,null,4,null,5,null,6]
示例 2:
输入:root = []
输出:[]
示例 3:
输入:root = [0]
输出:[0]
提示:
树中结点数在范围 [0, 2000] 内
-100 <= Node.val <= 100
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/flatten-binary-tree-to-linked-list
方法一:前序遍历 + 集合
由于展开之后,得到的是二叉树前序遍历的单链表,所以我们先通过前序遍历,将节点存放到集合list中,然后遍历这个集合,使得第 i - 1个节点的left变成null,right指向第 i 个节点。
class Solution {
public void flatten(TreeNode root) {
if(root == null)
return;
List<TreeNode> ans = new ArrayList<TreeNode>();
preOrderTraversal(root,ans);//将前序遍历的节点添加到ans集合中,然后遍历这个集合,形成一条单链表
int size = ans.size();
for(int i = 1; i < size; ++i){
TreeNode prev = ans.get(i - 1);
TreeNode cur = ans.get(i);
prev.left = null;
prev.right = cur;
}
}
public void preOrderTraversal(TreeNode root,List<TreeNode> ans){
if(root != null){
ans.add(root);
preOrderTraversal(root.left,ans);
preOrderTraversal(root.right,ans);
}
}
}
然而上面的方法空间复杂度为O(n),所以是否存在空间复杂度为O(1)的方案呢?所以就有了方法二。
方法二:边前序遍历边展开成单链表
定义一个变量pre表示上次遍历到的节点,如果pre不为null,就需要执行pre.left = null;pre.right = root的操作,从而实现形成单链表的操作。但是这里有一点需要提的是,因为存在着pre.right = root的操作,所以使得原先pre指向的right节点发生了变化,所以为了没有进行pre.right的操作之前pre的right节点,需要定义一个临时节点right,表示root的右子节点,如果没有这一步的话,就会导致输出结果不是我们想要的。
对应代码:
class Solution {
TreeNode pre = null;// 表示前序遍历获得的一个节点
public void flatten(TreeNode root) {
if(root != null){
TreeNode right = root.right;//获取这个节点的右子节点,这样下次遍历的时候,并不会因为pre.right的执行,从而使得,root.right受到影响
if(pre != null){
//如果pre不等于null,那么需要将pre的左子节点变成null,然后右子节点是当前这个节点
pre.left = null;
pre.right = root;
}
pre = root;
flatten(root.left);
flatten(right);
}
}
}