leetcode-二叉树总结

leetcode-104-二叉树的最大深度(maximum depth of binary tree)
**查询树的深度,一般有两种方法
第一种,递归,长度=当前长度(1)+左右子树的max长度
第二种,一层层遍历,建立两个list,now和next,now的初始值为root,length为0,每次循环将,now的left与right加入next,然后让now=next,并且length++,直到now为null为止

leetcode-98-验证二叉搜索树(validat binary search tree)-java
二叉搜索树是左侧子树全部比root小,右侧全比root大
验证二叉搜索树,二种方法
1 中序遍历,左侧大于右侧
2 递归 令左侧 大于min 小于root 右侧 大于root 小于min

leetcode-101-对称二叉树(symmetric tree)-java
树的话,验证对称,可以用递归
也可以迭代,在一个队列中加入两次root,每次取出两个,比较双方,然后一次塞入左的左,右的右,左的右,右的左

leetcode- 102二叉树的层次遍历(binary tree level order traversal)-java
二叉树层次遍历
迭代
建立一个queue,一个rowlist,

一个现在层次的节点数num=1,一个下一层next=0
将root加入queue
当queue不为空
从queue取出头,num–,将值加入rowlist,将left,right加入queue尾部,next++
当num为0 则num=next,next=0,结果加入rowlist,rowlist新建一个
递归
利用递归进行dfs,每次递归一个node的时候记录它的深度,然后在类中设置result变量,在result的第i层(i为节点深度),插入节点的值(先递归左孩子)

leetcode-108-将有序数组转换为二叉搜索树(convert sorted array to binary search tree)-java
二叉搜索树变为数组用中序遍历
数组变为平衡的二叉搜索树用递归
中间的为root
左边的比他小的数为左子树,右边的为右子树

leetcode-94-二叉树的中序遍历(binary tree inorder traversal)-java
解法1
同样用栈,用循环
循环条件是curt不为null或者stack不为空
首先将curt自身及所有左子树逐个加入stack
然后弹出stack顶部,将其加入result
最后curt为弹出的那个节点的右子树
因为弹出的那个节点,左子树要么没有,要么已经被处理,所以只考虑右子树
右子树如果没有,则下一次循环,直接curt = stack.pop();,相当于处理它的上层
如果有右子树,那么先处理该节点的右子树,再处理上层

解法2
中序递归方法,建立一个public void inorder(List result,TreeNode root)方法

leetcode-103-二叉树的锯齿形层次遍历(binary tree zigzag level order traversal)-java
解法1
使用典型的二叉树层次化遍历的方法,设置一个队列逐个放入层次化的二叉树,设置num,next变量控制层数,然后设置isLTR变量,表明该层的数据从左往右还是从右往左,如果从右往左,则每层遍历的节点由于是从左往右,则塞入linkedList的顺序是插入头部,导致最右的数据在list最左边
解法2
经典的递归算法,层次遍历二叉树时,可以根据参数depth,知道要加入到List < List < Integer > > 的哪个list的哪里

leetcode-105- 从前序与中序遍历序列构造二叉树(construct binary tree from preorder and inorder traversal)-java
从前序遍历可以得到该段的root,从中序遍历找到root,root的左右是root的左右节点,就可以按这个方法递归出这个节点的左右子节点
上述的做法有个缺点,每次都要去中序遍历中,查找中间节点的位置,因为数字不重复,可以将中序遍历放入hashmap,把中序遍历数组的每个元素的值和下标存起来,这样寻找根节点的位置就可以直接得到了

leetcode-116- 填充同一层的兄弟节点(populating next right pointers in each node
解法1
递归,传来自己与自己的next,设置next关系,然后让儿子处理儿子与侄子的关系
解法2
循环,pre为每一行的第一个节点,cur从pre开始,不断到同一行的next,如果没有next了,到下一行的第一个
每次处理cur的儿子与侄子的关系

leetcode-230-二叉搜索树中第K小的元素(kth smallest element in a bst)-java
解法1(成功,1ms,极快)
在solution中设置一个变量result,每个方法一旦发现现在第一小的数是自己,则把result=root.val
kthSmallest2方法,返回这个root下找到了几个数
同时right=kthSmallest2(root.right,k-left-1);,对右边的节点用k-left-1作为第几小的k

解法2
很有趣的题,但是很简单,实际上就是对树的中序遍历,关于第K小,因为是二叉搜索树,所以最左边的就是最小的,那么中序遍历的情况下,第一次回溯到中序就是最小的一个节点,每遍历一个元素就k–,当 k=1的时候,该节点就是第K小了。
通过循环的方式进行中序遍历,得到k变为1时,返回节点的val

leetcode-297-二叉树的序列化与反序列化 (seriliaze and deseriliaze binary tree)-java
使用先序遍历,null只加进去一次,不把null的左右加进去
1
2 3
4 5
变成1,2,4,null,null,5,null,null,3,null,null
反序列化,每次得到第一个str,然后删掉第一个。如果是null,就返回null。如果不是null,就创建节点,然后递归调用函数,设置左右节点。

leetcode-236-二叉树的最近公共祖先-java
解法2(别人的)
这种方法非常直观。先深度遍历改树。当你遇到节点 p 或 q 时,返回一些布尔标记。该标志有助于确定是否在任何路径中找到了所需的节点。最不常见的祖先将是两个子树递归都返回真标志的节点。它也可以是一个节点,它本身是p或q中的一个,对于这个节点,子树递归返回一个真标志。
从根节点开始遍历树。
如果当前节点本身是 p 或 q 中的一个,我们会将变量 mid 标记为 true,并继续搜索左右分支中的另一个节点。
如果左分支或右分支中的任何一个返回 true,则表示在下面找到了两个节点中的一个。
如果在遍历的任何点上,左、右或中三个标志中的任意两个变为 true,这意味着我们找到了节点 p 和 q 的最近公共祖先。
就是如果节点不是p或q的父节点,三个标志都为false。
只是一个的父节点,1个标志为true。
正好是2个的最近父节点,2个标志为true。
2个的非最近父节点,一定只有一个标志为true,因为2个节点一定在它的left或right侧。

解法3(别人的)
使用父指针迭代
如果每个节点都有父指针,那么我们可以从 p 和 q 返回以获取它们的祖先。在这个遍历过程中,我们得到的第一个公共节点是 LCA 节点。我们可以在遍历树时将父指针保存在字典中。
从根节点开始遍历树。
在找到 p 和 q 之前,将所有节点的父指针存储在字典中。
一旦我们找到了 p 和 q,我们就可以使用父亲字典获得 p 的所有祖先,并添加到一个称为祖先的集合中。
同样,我们遍历节点 q 的祖先。如果祖先存在于为 p 设置的祖先中,这意味着这是 p 和 q 之间的第一个共同祖先(同时向上遍历),因此这是 LCA 节点。

leetcode-124-二叉树中的最大路径和-java
初始化 max_sum 为最小可能的整数并调用函数 max_gain(node = root)。
实现 max_gain(node) 检查是继续旧路径还是开始新路径:
边界情况:如果节点为空,那么最大权值是 0 。
对该节点的所有孩子递归调用 max_gain,计算从左右子树的最大权值:left_gain = max(max_gain(node.left), 0) 和 right_gain = max(max_gain(node.right), 0)。
检查是维护旧路径还是创建新路径。创建新路径的权值是:price_newpath = node.val + left_gain + right_gain,当新路径更好的时候更新 max_sum。
对于递归返回的到当前节点的一条最大路径,计算结果为:node.val + max(left_gain, right_gain)。
即max_gain(node)方法,会返回node节点-它的某一侧的节点的最大长度。
某一侧节点-node-另一侧节点,这种情况的路径,会在方法内部更新max_sum字段,更新最大长度。

leetcode-208-实现Trie (前缀树)-java
TrieNode类,有isEnd字段,TrieNode[] nodes= new TrieNode[26]
Trie里有TrieNode类型的root,如果确实有一个字符 abc 那么root.nodes[0]不为null,而且最后一个nodes[2].isEnd为true

插入时,就node.nodes[now-‘a’] = new TrieNode(),新增一个节点,最后node.isEnd = true
查找时,不断node = node.nodes[now-‘a’],如果没有,则返回false,如果都有,就看最后node.isEnd是否为true

leetcode-114-二叉树展开为链表-java
解法1
寻找前驱节点
前两种方法都借助前序遍历,前序遍历过程中需要使用栈存储节点。有没有空间复杂度是 O(1)的做法呢?
注意到前序遍历访问各节点的顺序是根节点、左子树、右子树。如果一个节点的左子节点为空,则该节点不需要进行展开操作。如果一个节点的左子节点不为空,则该节点的左子树中的最后一个节点被访问之后,该节点的右子节点被访问。该节点的左子树中最后一个被访问的节点是左子树中的最右边的节点,也是该节点的前驱节点。因此,问题转化成寻找当前节点的前驱节点。
具体做法是,对于当前节点,如果其左子节点不为空,则在其左子树中找到最右边的节点,作为前驱节点,将当前节点的右子节点赋给前驱节点的右子节点,然后将当前节点的左子节点赋给当前节点的右子节点,并将当前节点的左子节点设为空。对当前节点处理结束后,继续处理链表中的下一个节点,直到所有节点都处理结束。

解法2
前序遍历和展开同步进行
使用方法一的前序遍历,由于将节点展开之后会破坏二叉树的结构而丢失子节点的信息,因此前序遍历和展开为单链表分成了两步。能不能在不丢失子节点的信息的情况下,将前序遍历和展开为单链表同时进行?
之所以会在破坏二叉树的结构之后丢失子节点的信息,是因为在对左子树进行遍历时,没有存储右子节点的信息,在遍历完左子树之后才获得右子节点的信息。只要对前序遍历进行修改,在遍历左子树之前就获得左右子节点的信息,并存入栈内,子节点的信息就不会丢失,就可以将前序遍历和展开为单链表同时进行。
该做法不适用于递归实现的前序遍历,只适用于迭代实现的前序遍历。修改后的前序遍历的具体做法是,每次从栈内弹出一个节点作为当前访问的节点,获得该节点的子节点,如果子节点不为空,则依次将右子节点和左子节点压入栈内(注意入栈顺序)。
展开为单链表的做法是,维护上一个访问的节点 prev,每次访问一个节点时,令当前访问的节点为 curr,将 prev 的左子节点设为 null 以及将 prev 的右子节点设为 curr,然后将 curr 赋值给 prev,进入下一个节点的访问,直到遍历结束。需要注意的是,初始时 prev 为 null,只有在 prev 不为 null 时才能对 prev 的左右子节点进行更新。

解法3
nodeToListNode方法返回TreeNode数组,第一个元素为root对应链表的开头,第二个元素为root对应链表的结束。
nodeToListNode方法内部对left和right递归调用nodeToListNode方法,然后root-leftList开头-leftList末尾-rightList开头-rightList末尾

leetcode-226-翻转二叉树-java
解法1
这是一个非常经典的树的问题,这个问题很适合用递归方法来解决。
反转一颗空树结果还是一颗空树。对于一颗根为 r,左子树为 mboxright, 右子树为 mboxleft 的树来说,它的反转树是一颗根为 r,左子树为 mboxright 的反转树,右子树为 mboxleft 的反转树的树。

解法2(别人的)
我们也可以用迭代方法来解决这个问题,这种做法和深度优先搜索(Breadth-fist Search, BFS)很接近。
这个方法的思路就是,我们需要交换树中所有节点的左孩子和右孩子。因此可以创一个队列来存储所有左孩子和右孩子还没有被交换过的节点。开始的时候,只有根节点在这个队列里面。只要这个队列不空,就一直从队列中出队节点,然后互换这个节点的左右孩子节点,接着再把孩子节点入队到队列,对于其中的空节点不需要加入队列。最终队列一定会空,这时候所有节点的孩子节点都被互换过了,直接返回最初的根节点就可以了。
动画 https://leetcode-cn.com/problems/invert-binary-tree/solution/dong-hua-yan-shi-liang-chong-shi-xian-226-fan-zhua/

leetcode-538-把二叉搜索树转换为累加树-java
解法1
本题中要求我们将每个节点的值修改为原来的节点值加上所有大于它的节点值之和。这样我们只需要反序中序遍历该二叉搜索树,记录过程中的节点值之和,并不断更新当前遍历到的节点的节点值,即可得到题目要求的累加树。

解法2
Morris 遍历
有一种巧妙的方法可以在线性时间内,只占用常数空间来实现中序遍历。这种方法由 J. H. Morris 在 1979 年的论文「Traversing Binary Trees Simply and Cheaply」中首次提出,因此被称为 Morris 遍历。
Morris 遍历的核心思想是利用树的大量空闲指针,实现空间开销的极限缩减。其反序中序遍历规则总结如下:

如果当前节点的右子节点为空,处理当前节点,并遍历当前节点的左子节点;
如果当前节点的右子节点不为空,找到当前节点右子树的最左节点(该节点为当前节点中序遍历的前驱节点);
如果最左节点的左指针为空,将最左节点的左指针指向当前节点,遍历当前节点的右子节点;
如果最左节点的左指针不为空,将最左节点的左指针重新置为空(恢复树的原状),处理当前节点,并将当前节点置为其左节点;

重复步骤 1 和步骤 2,直到遍历结束。
这样我们利用 Morris 遍历的方法,反序中序遍历该二叉搜索树,即可实现线性时间与常数空间的遍历。

leetcode-543-二叉树的直径-java
最大直径路径不一定是穿过根节点的,所以要设置一个变量max,用来记录所有的子树的直径,然后更新最大值。

设置一个全局变量max;
对root进行求最大深度,调用下maxDeepth方法;
越过叶子节点,返回0;
计算左子树最大深度left;
计算右子树最大深度right;

(在这个位置:计算直径 = left + right , 然后再维护最大值max)
返回左右子树较大者 + 1;

leetcode-617-合并二叉树-java
解法1
可以使用深度优先搜索合并两个二叉树。从根节点开始同时遍历两个二叉树,并将对应的节点进行合并。
两个二叉树的对应节点可能存在以下三种情况,对于每种情况使用不同的合并方式。

如果两个二叉树的对应节点都为空,则合并后的二叉树的对应节点也为空;
如果两个二叉树的对应节点只有一个为空,则合并后的二叉树的对应节点为其中的非空节点;
如果两个二叉树的对应节点都不为空,则合并后的二叉树的对应节点的值为两个二叉树的对应节点的值之和,此时需要显性合并两个节点。
对一个节点进行合并之后,还要对该节点的左右子树分别进行合并。这是一个递归的过程。

解法2
广度优先搜索
也可以使用广度优先搜索合并两个二叉树。首先判断两个二叉树是否为空,如果两个二叉树都为空,则合并后的二叉树也为空,如果只有一个二叉树为空,则合并后的二叉树为另一个非空的二叉树。

如果两个二叉树都不为空,则首先计算合并后的根节点的值,然后从合并后的二叉树与两个原始二叉树的根节点开始广度优先搜索,从根节点开始同时遍历每个二叉树,并将对应的节点进行合并。
使用三个队列分别存储合并后的二叉树的节点以及两个原始二叉树的节点。初始时将每个二叉树的根节点分别加入相应的队列。每次从每个队列中取出一个节点,判断两个原始二叉树的节点的左右子节点是否为空。如果两个原始二叉树的当前节点中至少有一个节点的左子节点不为空,则合并后的二叉树的对应节点的左子节点也不为空。对于右子节点同理。

如果合并后的二叉树的左子节点不为空,则需要根据两个原始二叉树的左子节点计算合并后的二叉树的左子节点以及整个左子树。考虑以下两种情况:
如果两个原始二叉树的左子节点都不为空,则合并后的二叉树的左子节点的值为两个原始二叉树的左子节点的值之和,在创建合并后的二叉树的左子节点之后,将每个二叉树中的左子节点都加入相应的队列;
如果两个原始二叉树的左子节点有一个为空,即有一个原始二叉树的左子树为空,则合并后的二叉树的左子树即为另一个原始二叉树的左子树,此时也不需要对非空左子树继续遍历,因此不需要将左子节点加入队列。
对于右子节点和右子树,处理方法与左子节点和左子树相同。

剑指offer-6-重建二叉树-java
前序 mid,left,right
中序 left,mid,right
1 通过前序的mid,找到中序的mid,因为所有节点值不同,通过hashmap一步找到mid的inIndex
2 找到中序的mid位置,得到left和right的size,从而得到前序里left和right的范围
3 不断循环,left再分成3份,返回left的mid。。。

剑指offer-18-树的子结构-java
设立match方法,直接比较t1和t2两个树是否完全相同,方法内先比较t1和t2的val,然后返回match(t1.left,t2.left) && match(t1.right,t2.right)
hasSubtree方法,先比较t1和t2的val,如果相同,调用match(t1, t2),如果成功,则返回true。否则,返回hasSubtree(t1.left, t2) || hasSubtree(t1.right, t2)

剑指offer-19-二叉树的镜像-java
我们先先序遍历这个树的子结点,如果子结点有子结点,那么就交换,当交换完成所有的非叶子结点,那么就是我们所要求的镜像。

剑指offer-23-从上往下打印二叉树-java
按照层次遍历的方法,使用队列辅助。
1.将根结点加入队列。
2.循环出队,打印当前元素,若该结点有左子树,则将其加入队列,若有右子树,将其加入队列。
3.直到队列为空,表明已经打印完所有结点。

剑指offer-24-二叉搜索树的后序遍历序列-java
在后续遍历得到的序列中,最后一个数字是树的根节点的值。数组中前面的数字可以分成两部分:第一部分是左子树节点的值,他们都比根节点的值小;第二部分是右子树节点的值,它们都比根节点的值大。以数组 {5,7,6,9,11,10,8}为例,后续遍历结构的最后一个数字8就是根节点的值。在这个数组中,前3个数字5、7和6都比8小,是值为8的节点的左子树节点;后3个数字9、11、10都比8大,是值为8的节点的右子树节点。

接下来我们用同样的方法确定与数组每一部分对应的子树结构。这其实就是一个递归的过程。对于序列5,7,6,最后一个数字6是左子树的根节点的值。数字5比6小,是值为6的节点的左子节点,而7则是右子节点。同样,在序列9、11、10中,最后一个数字10是右子树的根节点,数字9比10小,是值为10的节点的左子节点,而11则是它的右子节点。

我们再来分析另一个整数数组{7,4,6,5}。后续遍历的最后一个数是根节点,因此根节点值是5.由于第一个数字7大于5,因此在对应的二叉搜索树中,根节点上是没有左子树的,数字7,4,6都是右子树节点的值。但在右子树中有一个节点的值是4,比根节点5的值小,这违背了二叉搜索树的定义,因此不存在一个二叉搜索树与该数组对应。

剑指offer-25-二叉树中和为某一值的路径-java
要求路径,首先想到遍历
因为首先要访问根结点,所以选用前序遍历
由于遍历过程中无法找到结点的前一个结点,所以想到用一种数据结构来保存路径序列
当遍历到叶子节点但是没有找到的时候,要退回到上一节点

这道题我是按照回溯的思路去做的,我们需要一个数据结构来保存和删除当前递归函数中添加的值。这里要打印一条路径,我们可以选择List、栈等,它们都可以很方便的删除掉末尾的元素从而保护现场,也可以选择String,只需要在进入递归的时候创建一个和参数一样的值,也能做到在子函数退出的时候保护现场的效果。递归的时候先判断是否满足条件,然后添加本节点的值往下递归。

剑指offer-27-二叉搜索树与双向链表-java
由于转换为双向链表之后的链表是排过序的,所以需要对二叉树进行中序遍历
根结点的左孩子要指向左子树的最大值,右孩子要指向有子树的最小值
convertNode(TreeNode root, TreeNode[] last),last对应的节点是以root为根的二叉树对应的链表的左边节点。

剑指offer-39-二叉树的深度-java
如果一棵树只有一个结点,它的深度为1,如果根节点只有左子树而没有右子树,那么树的深度应该是其左子树的深度+1.同样如果根节点只有右子树而没有左子树,那么树的深度应该是其右子树+1.如果既有左子树又有右子树,那概述的深度就是左、右子树的深度的较大值加1.。
利用这个思路,我们可以用递归来实现代码

剑指offer-50-树中两个节点的最低公共祖先-java
如果是二叉搜索树的话,我们只需从根结点判断,如果二结点与根的左右子树比较一大一小,那么跟结点就是二者最低公共祖先;如果二结点都比左子结点小,向左子树递归进行比较;如果二结点都比右子树结点大,向右子树递归进行比较;
如果不是二叉搜索树,但带有指向父节点的指针,那么此题转换成在两个有相交的链表上求第一个相交点。
如果不带指向父节点的指针,那么我们可以记录从根结点到这两个结点的路径即可求出。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值