树
- 节点深度:对任意节点x,x节点的深度表示为根节点到x节点的路径长度。所以根节点深度为0,第二层节点深度为1,以此类推
- 树的深度:一棵树中节点的最大深度就是树的深度,也称为高度
- 深度为K的二叉树:最多2k-1个结点,第k层最多2(k-1)个结点。
- 二叉树中,叶子结点数 = 度为2的结点数 + 1
1、满二叉树:
叶子节点都在同一层并且除叶子节点外的所有节点都有两个子节点。
2、完全二叉树
对于一颗二叉树,假设其深度为d(d>1)。除第d层外的所有节点构成满二叉树,且第d层所有节点从左向右连续地紧密排列,这样的二叉树被称为完全二叉树;
PS:这里的满二叉树和完全二叉树取的是国内的定义,国外的定义不一样,有兴趣的可以去看看国外的定义。
3、二叉查找树(二叉搜索树、BST)
若任意节点的左子树不空,则左子树上所有节点的值均小于它的根节点的值;
若任意节点的右子树不空,则右子树上所有节点的值均大于它的根节点的值;
任意节点的左、右子树也分别为二叉查找树;
没有键值相等的节点。
4、平衡二叉树(AVL)
特殊的二叉排序树,它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树,同时,平衡二叉树必定是二叉搜索树。
5、哈夫曼树
带权路径最短的二叉树:根据一系列权值,各个叶子节点的权值乘以其到根结点的路劲长度之和最小的数
6、B-树
7、B+数
8、红黑树
(1)94、144、145、树的遍历-中等
题目
树的前中后序遍历
思路:递归实现
遍历一个结点,如果之前没有遍历过就将其标记为“ 没遍历过 ”,如果之前遍历过则将其pop出来并记录val。
代码
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
ArrayList<Integer> res = new ArrayList<>();
if(root == null) return res;
res = postorder(root, res);
return res;
}
public ArrayList<Integer> postorder(TreeNode root, ArrayList<Integer> list){
//前、中、后序遍历只需交换下面三个顺序
if(root == null) return list;
list = postorder(root.left, list);
list = postorder(root.right, list);
list.add(root.val);
return list;
}
}
思路:标记法的栈实现
- 每一列是否能存放的雨水由该列前后的最高柱子决定,如果前后最高柱子中较矮的柱子高度大于该列柱子高度,则该列存放的雨水等于较矮柱子高度减该列柱子的高度;如果小于等于该列柱子高度则该列无法存水。
遍历一个结点,如果之前没有遍历过就将其标记为“ 没遍历过 ”,如果之前遍历过则将其pop出来并记录val。
代码
List<Integer> res = new ArrayList<>();
HashMap<TreeNode, Boolean> map = new HashMap<>();
if(root == null) return res;
Deque<TreeNode> stack = new ArrayDeque<>();//用来标记此结点是否遍历过,true表示被遍历过
stack.push(root);
map.put(root, false);
while(!stack.isEmpty()){
TreeNode node = stack.pop();
if(!map.get(node)){
//前、中、后序遍历只需交换下面三个顺序
if(node.right != null){
stack.push(node.right);
map.put(node.right, false);
}
stack.push(node);
map.put(node, true);
if(node.left != null){
stack.push(node.left);
map.put(node.left, false);
}
}else{
res.add(node.val);
}
}
return res;
思路:常规栈实现
代码(中序)
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
if(root == null) return res;
Deque<TreeNode> stack = new ArrayDeque<>();
while(!stack.isEmpty() || root != null){
while(root != null){
stack.push(root);
root = root.left;
}
TreeNode top = stack.pop();
res.add(top.val);
root = top.right;
}
return res;
}
}
代码(前序)
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
if(root == null) return res;
Deque<TreeNode> stack = new ArrayDeque<>();
while(!stack.isEmpty() || root != null){
while(root != null){
res.add(root.val);
stack.push(root);
root = root.left;
}
root = stack.pop().right;
}
return res;
}
}
代码(后序)
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
if(root == null) return res;
Deque<TreeNode> stack = new ArrayDeque<>();
TreeNode prev = null;
while(!stack.isEmpty() || root != null){
while(root != null){
stack.push(root);
root = root.left;
}
TreeNode top = stack.pop();
/******************************
如果右边不为空显然要将中间的重新入栈继续从右边子树开始
但这里得考虑右边子树遍历完并且右节点出栈将值加入到res后,下一个出栈的显然是中间节点
但这时又会重新判断该节点右边是否为空,显然不为空,又要进入右边子树开始遍历,永远就出不来了。所以
我们不能简单判断右边是否为空,还必须判断右边的结点是否是上一个才出栈的结点(表示已经进入右边子树遍历过一遍了),
如果是则不用再继续从右边子树开始
***********************************/
if(top.right != null && top.right != prev){
stack.push(top);
root = top.right;
}else{
res.add(top.val);
prev = top;
}
}
return res;
}
}
思路:Morris遍历
记作当前节点为cur。
-
如果cur无左孩子,cur向右移动(cur=cur.right)
-
如果cur有左孩子,找到cur左子树上最右的节点,记为mostright
-
- 如果mostright的right指针指向空,让其指向cur,cur向左移动(cur=cur.left)
- 如果mostright的right指针指向cur,让其指向空,cur向右移动(cur=cur.right)
代码:前序
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
TreeNode currentNode = root;
TreeNode mostRight;
List<Integer> list = new ArrayList<>();
if(root == null) return list;
while(currentNode != null){
mostRight = currentNode.left;
//当前节点的左节点不为空,为空当前节点就等于右结点。
if(mostRight != null){
//找到当前节点左子树的最右节点
while(mostRight.right!=null && mostRight.right!=currentNode)
mostRight = mostRight.right;
//最右结点的右结点为空:最右结点的右结点就指向当前节点,当前节点等于左结点
//最右结点的右结点指向当前节点:说明已经遍历过了,恢复最右结点的右指针指为null,当前节点等于右结点
if(mostRight.right == null){
mostRight.right = currentNode;
list.add(currentNode.val);
currentNode = currentNode.left;
}else{
mostRight.right = null;
currentNode = currentNode.right;
//注意:如果mostRight.right == currentNode,表明currentNode已经遍历过了。是从底部回来的过程
}
}else{
list.add(currentNode.val);
currentNode = currentNode.right;
}
}
return list;
}
}
代码:中序
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
TreeNode currentNode = root;
TreeNode mostRight;
List<Integer> list = new ArrayList<>();
if(root == null) return list;
while(currentNode != null){
mostRight = currentNode.left;。
if(mostRight != null){
while(mostRight.right!=null && mostRight.right!=currentNode)
mostRight = mostRight.right;
if(mostRight.right == null){
/*---------------------------*/
/* 访问数据操作 主结点
/*---------------------------*/
mostRight.right = currentNode;
currentNode = currentNode.left;
}else{
/*---------------------------*/
/* 访问数据操作 右结点
/*---------------------------*/
mostRight.right = null;
list.add(currentNode.val);
currentNode = currentNode.right;
}
}else{
/*---------------------------*/
/* 访问数据操作 左结点
/*---------------------------*/
list.add(currentNode.val);
currentNode = currentNode.right;
}
}
return list;
}
}
(2)102、二叉树层序遍历
题目
给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。
思路:队列实现
代码
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
Queue<TreeNode> queue = new ArrayDeque<>();
//辅助队列,用于把每一层分开存储。
Queue<TreeNode> queue2 = new ArrayDeque<>();
List<List<Integer>> res = new ArrayList<>();
List<Integer> list = new ArrayList<>();
if(root == null) return res;
queue.offer(root);
while(!queue.isEmpty()){
while(!queue.isEmpty()){
TreeNode tree = queue.poll();
list.add(tree.val);
if(tree.left != null) queue2.offer(tree.left);
if(tree.right != null) queue2.offer(tree.right);
}
while(!queue2.isEmpty()){
queue.offer(queue2.poll());
}
res.add(list);
list = new ArrayList<Integer>();
}
return res;
}
}
(3)103、二叉树锯齿层序遍历
题目
给定一个二叉树,返回其节点值的锯齿形层序遍历。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。
思路:和前一道题一样,只不过多了个判断是否将list翻转
代码
class Solution {
public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
Queue<TreeNode> queue = new ArrayDeque<>();
//辅助队列,用于把每一层分开存储。
Queue<TreeNode> queue2 = new ArrayDeque<>();
List<List<Integer>> res = new ArrayList<>();
List<Integer> list = new ArrayList<>();
int flag = 0;
if(root == null) return res;
queue.offer(root);
while(!queue.isEmpty()){
while(!queue.isEmpty()){
TreeNode tree = queue.poll();
list.add(tree.val);
if(tree.left != null) queue2.offer(tree.left);
if(tree.right != null) queue2.offer(tree.right);
}
while(!queue2.isEmpty()){
queue.offer(queue2.poll());
}
//判断是否需要翻转
if(flag == 1){
Collections.reverse(list);
flag = 0;
}else{
flag = 1;
}
res.add(list);
list = new ArrayList<Integer>();
}
return res;
}
}
一、二叉搜索树
(1) 98、验证二叉搜索树-中等
题目
验证二叉搜索树
思路:中序遍历:递归实现
代码
class Solution {
long prev = Long.MIN_VALUE;
public boolean isValidBST(TreeNode root) {
if(root == null) return true;
if(!isValidBST(root.left)) return false;
if(prev >= root.val) return false;
prev = root.val;
return isValidBST(root.right);
}
}
思路:中序遍历:栈实现
代码
class Solution {
long prev = Long.MIN_VALUE;
public boolean isValidBST(TreeNode root) {
if(root == null) return true;
Deque<TreeNode> stack = new ArrayDeque<>();
while(!stack.isEmpty() || root != null){
while(root != null){
stack.push(root);
root = root.left;
}
TreeNode top = stack.pop();
if(prev >= top.val) return false;
prev = top.val;
root = top.right;
}
return true;
}
}
思路:递归
1、一个节点必须大于左结点的值和小于右结点的值,2、同时还必须小于该结点位于的左子树的最小值,大于该结点位于的右子树最小值
代码
/*******只满足1条件是不行的*************
boolean isValidBST(TreeNode root) {
if (root == null) return true;
if (root.left != null && root.val <= root.left.val) return false;
if (root.right != null && root.val >= root.right.val) return fals
e;
return isValidBST(root.left) && isValidBST(root.right);
}
****************************/
//不局限于只比较结点的左右孩子结点大小
boolean isValidBST(TreeNode root) {
return isValidBST(root, null, null);
}
boolean isValidBST(TreeNode root, TreeNode min, TreeNode max) {
if (root == null) return true;
if (min != null && root.val <= max.val) return false;
if (max != null && root.val >= min.val) return false;
return isValidBST(root.left, root, max)//右结点必须大于于右子树最大值
&& isValidBST(root.right, min, root);//左结点必须小于左子树最小值
}
(2) 增
public TreeNode addNode(TreeNode root, int val){
if(root == nul) return new TreeNode(val);
if(val < root.val){
root.left = addNode(root.left, val);
}else{
root.right = addNode(root.right, val);
}
}
(3) 删
TreeNode deleteNode(TreeNode root, int key) {
if (root == null) return null;
if (root.val == key) {
// 左右结点有一个为空或两个都空直接删除
if (root.left == null) return root.right;
if (root.right == null) return root.left;
// 处理情况 3
TreeNode minNode = getMin(root.right);
root.val = minNode.val;
root.right = deleteNode(root.right, minNode.val);
} else if (root.val > key) {
root.left = deleteNode(root.left, key);
} else if (root.val < key) {
root.right = deleteNode(root.right, key);
}
return root;
}
TreeNode getMin(TreeNode node) {
// BST 最左边的就是最⼩的
while (node.left != null) node = node.left;
return node;
}
95、不同的二叉搜索树 II-中等
题目
给定一个整数 n,生成所有由 1 … n 为节点所组成的 二叉搜索树 。
思路:递归实现
解法一完全没有用到查找二叉树的性质,暴力尝试了所有可能从而造成了重复。我们可以利用一下查找二叉树的性质。左子树的所有值小于根节点,右子树的所有值大于根节点。
所以如果求 1…n 的所有可能。
我们只需要把 1 作为根节点,[ ] 空作为左子树,[ 2 … n ] 的所有可能作为右子树。
2 作为根节点,[ 1 ] 作为左子树,[ 3…n ] 的所有可能作为右子树。
3 作为根节点,[ 1 2 ] 的所有可能作为左子树,[ 4 … n ] 的所有可能作为右子树,然后左子树和右子树两两组合。
4 作为根节点,[ 1 2 3 ] 的所有可能作为左子树,[ 5 … n ] 的所有可能作为右子树,然后左子树和右子树两两组合。
…
n 作为根节点,[ 1… n ] 的所有可能作为左子树,[ ] 作为右子树。
至于,[ 2 … n ] 的所有可能以及 [ 4 … n ] 以及其他情况的所有可能,可以利用上边的方法,把每个数字作为根节点,然后把所有可能的左子树和右子树组合起来即可。
如果只有一个数字,那么所有可能就是一种情况,把该数字作为一棵树。而如果是 [ ],那就返回 null。
代码
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public List<TreeNode> generateTrees(int n) {
if(n < 1) return null;
return generateTrees(1, n);
}
public List<TreeNode> generateTrees(int start, int end){
List<TreeNode> list = new ArrayList<TreeNode>();
if(start > end){
list.add(null);
return list;
}
if(start == end){
TreeNode node = new TreeNode(start);
list.add(node);
return list;
}
for(int i = start; i <= end; i++){
List<TreeNode> leftList = generateTrees(start, i - 1);
List<TreeNode> rightList = generateTrees(i + 1, end);
for(TreeNode leftNode: leftList){
for(TreeNode rightNode: rightList){
TreeNode root = new TreeNode(i);
root.left = leftNode;
root.right = rightNode;
list.add(root);
}
}
}
return list;
}
}
96、不同的二叉搜索树 -中等
题目
给定一个整数 n,生成所有由 1 … n 为节点所组成的 二叉搜索树 种类。
思路:动态规划(自顶向下)
和95题类似,95是需要保存每个子树根结点,所有必须返回list,这题只需要返回一个int类型的count表示有多少颗子树即可。
DP优化:因为二叉搜索数的种类与结点个数有关,相同结点个数组成的二叉搜索树种类的个数一样,所有我们递归时可以保存起已经求出来的对应结点个数组成的二叉树种类的个数。
代码
class Solution {
public int numTrees(int n) {
if(n < 1) return 0;
int flag[] = new int[n];
return numTrees(1, n, flag);//保存指定个数的结点可以组成的二叉搜索树种类个数。
}
public int numTrees(int begin, int end, int[] flag){
int sum = 0;
if(begin >= end){
return 1;
}
if(flag[end-begin] != 0){
return flag[end-begin];
}
for(int i = begin; i <= end; i++){
int leftCount = numTrees(begin, i-1, flag);
int rightCount = numTrees(i+1, end, flag);
sum += leftCount * rightCount;
}
flag[end-begin] = sum;
return sum;
}
}
思路:动态规划(自底向上)
-
假设 n 个节点存在二叉排序树的个数是 G (n),令 f(i) 为以 i 为根的二叉搜索树的个数,则
G(n) = f(1) + f(2) + f(3) + f(4) + … + f(n)G(n)=f(1)+f(2)+f(3)+f(4)+…+f(n) -
当 i 为根节点时,其左子树节点个数为 i-1 个,右子树节点为 n-i,则
f(i) = G(i-1)*G(n-i)f(i)=G(i−1)∗G(n−i) -
综合两个公式可以得到 卡特兰数 公式
G(n) = G(0)*G(n-1)+G(1)*G(n-2)+…+G(n-1)*G(0)
代码
class Solution {
public int numTrees(int n) {
//dp[i]表示i个结点可以组成的二叉树种类个数
//G(n) = G(0)*G(n-1)+G(1)*G(n-2)+...+G(n-1)*G(0)
int[] dp = new int[n+1];
dp[0] = 1;
dp[1] = 1;
for(int i = 2; i <= n; i++){
for(int j = 0; j < i; j++){
dp[i] += dp[j]*dp[i-j-1];
}
}
return dp[n];
}
}
99. 恢复二叉搜索树-中等
题目
给你二叉搜索树的根节点 root ,该树中的两个节点被错误地交换。请在不改变其结构的情况下,恢复这棵树。
进阶:使用 O(n) 空间复杂度的解法很容易实现。你能想出一个只使用常数空间的解决方案吗?
思路:中序遍历一遍,找到两个被交换的数据,然后再中序遍历把对数据交换回来
两个结点交换后中序遍历结果有两种情况:
- 紧挨结点被交换,只有一对数据不是升序。:1、2、3、4中,2与3交换,只有3、2不满足升序
- 不相邻结点被交换,有两对数据不是升序:
1、2、3、4、5中,2和4交换,1、4、3、2、5,这样4与3、3与2都不满足升序,第一对不满足升序的数据中(num1 > num2)num1为被交换的数据,遍历到第二对不满足升序的数据时,num2为被交换的数据。
总结:需要考虑是否为相邻结点被交换,然后保存对应交换结点,最后将其值交换回来。
代码
class Solution {
public void recoverTree(TreeNode root) {
int num1 = 0, num2 = 0, flag = 0;
ArrayList<Integer> list = postorder(root, new ArrayList<Integer>());
Integer arr[] = list.toArray(new Integer[0]);
//根据中序遍历结果找到被交换的两个数据
for(int i = 0; i < arr.length-1; i++){
if(arr[i] > arr[i+1]){
if(flag == 0){
num1 = i;
flag = 1;
}else{
num2 = arr[i+1];
flag = 2;
break;
}
}
}
if(flag == 1){
num2 = arr[num1+1];
num1 = arr[num1];
}else{
num1 = arr[num1];
}
recoverTree(root, num1, num2);
}
//中序遍历第一遍
public ArrayList<Integer> postorder(TreeNode root, ArrayList<Integer> list){
if(root == null) return list;
list = postorder(root.left, list);
list.add(root.val);
list = postorder(root.right, list);
return list;
}
//中序遍历第二遍,找到对应数据的结点交换值
public void recoverTree(TreeNode root, int num1, int num2){
if(root == null) return ;
recoverTree(root.left, num1, num2);
if(root.val == num1){
root.val = num2;
}
else{
if(root.val == num2) root.val = num1;
}
recoverTree(root.right, num1, num2);
}
}
思路:栈或递归中序遍历实现
代码
/*-------对结点的操作--------*/
if (prev != null && currentNode.val < prev.val) {
tempNode2 = currentNode;
if (tempNode1 == null) {
tempNode1 = prev;
}
}
prev = currentNode;
/*---------------------------*/
思路:Morris遍历实现
代码
class Solution {
public void recoverTree(TreeNode root) {
TreeNode currentNode = root;
TreeNode mostRight;
TreeNode prev = null, tempNode1 = null, tempNode2 = null;
if(root == null) return ;
while(currentNode != null){
mostRight = currentNode.left;
if(mostRight != null){
while(mostRight.right!=null && mostRight.right!=currentNode)
mostRight = mostRight.right;
if(mostRight.right == null){
/*---------------------------*/
/* 访问结点操作部分,中序遍历就不在这访问结点
/*---------------------------*/
mostRight.right = currentNode;
currentNode = currentNode.left;
}else{
/*---------------------------*/
if (prev != null && currentNode.val < prev.val) {
tempNode2 = currentNode;
if (tempNode1 == null) {
tempNode1 = prev;
}
}
prev = currentNode;
/*------------------------*/
mostRight.right = null;
currentNode = currentNode.right;
}
}else{
/*---------------------------*/
if (prev != null && currentNode.val < prev.val) {
tempNode2 = currentNode;
if (tempNode1 == null) {
tempNode1 = prev;
}
}
prev = currentNode;
/*---------------------------*/
currentNode = currentNode.right;
}
}
int temp = tempNode1.val;
tempNode1.val = tempNode2.val;
tempNode2.val = temp;
}
}
95、相同的树 -简单
题目
判断两颗数是否相同
思路:递归实现
代码
class Solution {
public boolean isSameTree(TreeNode p, TreeNode q) {
if(p == null && q == null) return true;
if(p == null || q == null) return false;
if(p.val != q.val) return false;
return isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
}
}
104、二叉树的最大深度 -简单
题目
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
思路:递归实现
代码
class Solution {
public int maxDepth(TreeNode root) {
if(root == null) return 0;
int leftDepth = maxDepth(root.left);
int rightDepth = maxDepth(root.right);
return Math.max(leftDepth, rightDepth) + 1;
}
}
105、从前序与中序遍历序列构造二叉树 -中等
题目
根据一棵树的前序遍历与中序遍历构造二叉树。
注意:
你可以假设树中没有重复的元素。
思路:递归
- 前序第一个可以确定当前树的根结点,根据确定的根结点(由于没有重复元素)找到其在中序中的位置,这样我们可以知道中序中该位置左边是左子树的中序遍历,右边是右子树的中序遍历。而又因为一颗树的前序、中序遍历结果的元素个数是一样的,所以可由前面确定的左右子树中序遍历的元素个数确定左右子树的前序遍历。
- 这样最后问题就变为:前序、中序求根结点---->由左子树的前序、中序求根结点,由右子树前序、中序求根结点。一直递归到前序与中序的元素个数为0,返回null。
代码
class Solution {
private Map<Integer, Integer> indexMap;
public TreeNode myBuildTree(int[] preorder, int[] inorder, int preorder_left, int preorder_right, int inorder_left, int inorder_right) {
if (preorder_left > preorder_right) {
return null;
}
// 前序遍历中的第一个节点就是根节点
int preorder_root = preorder_left;
// 在中序遍历中定位根节点
int inorder_root = indexMap.get(preorder[preorder_root]);
// 先把根节点建立出来
TreeNode root = new TreeNode(preorder[preorder_root]);
// 得到左子树中的节点数目
int size_left_subtree = inorder_root - inorder_left;
// 递归地构造左子树,并连接到根节点
// 先序遍历中「从 左边界+1 开始的 size_left_subtree」个元素就对应了中序遍历中「从 左边界 开始到 根节点定位-1」的元素
root.left = myBuildTree(preorder, inorder, preorder_left + 1, preorder_left + size_left_subtree, inorder_left, inorder_root - 1);
// 递归地构造右子树,并连接到根节点
// 先序遍历中「从 左边界+1+左子树节点数目 开始到 右边界」的元素就对应了中序遍历中「从 根节点定位+1 到 右边界」的元素
root.right = myBuildTree(preorder, inorder, preorder_left + size_left_subtree + 1, preorder_right, inorder_root + 1, inorder_right);
return root;
}
public TreeNode buildTree(int[] preorder, int[] inorder) {
int n = preorder.length;
// 构造哈希映射,帮助我们快速定位根节点
indexMap = new HashMap<Integer, Integer>();
for (int i = 0; i < n; i++) {
indexMap.put(inorder[i], i);
}
return myBuildTree(preorder, inorder, 0, n - 1, 0, n - 1);
}
}
106、从中序与后序遍历序列构造二又树 -中等
题目
根据一棵树的后序遍历与中序遍历构造二叉树。
注意:
你可以假设树中没有重复的元素。
思路:递归
和上一题一样的递归思路,只是当前根结点的值变成从后序遍历的最后一个取。
代码
class Solution {
private Map<Integer, Integer> map = new HashMap<>();
public TreeNode buildTree(int[] inorder, int[] postorder) {
for(int i = 0; i < inorder.length; i++){
map.put(inorder[i], i);
}
return buildTree(inorder, postorder, 0, inorder.length-1, 0, postorder.length-1);
}
public TreeNode buildTree(int[] inorder, int[] postorder, int inorderStart, int inorderEnd, int postorderStart, int postorderEnd) {
System.out.println(postorderStart+","+postorderEnd);
if(postorderStart - postorderEnd > 0) return null;
int val = postorder[postorderEnd];//后续最后一个值为当前root的val
TreeNode root = new TreeNode(val, null, null);
if(postorderStart == postorderEnd) return root;
int index = map.get(val);//根结点在中序里的位置
int size = index - inorderStart;//根据中序找出左子树元素个数
System.out.println("index:"+index+"size:"+size);
root.left = buildTree(inorder, postorder, inorderStart, index-1,postorderStart, postorderStart+size-1);
root.right = buildTree(inorder, postorder, index+1, inorderEnd,postorderStart+size,postorderEnd-1);
return root;
}
}
107、二叉树的层序遍历 II -中等
题目
给定一个二叉树,返回其节点值自底向上的层序遍历。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)
思路:正常层序遍历,最后把list反转
代码
class Solution {
public List<List<Integer>> levelOrderBottom(TreeNode root) {
Queue<TreeNode> queue = new ArrayDeque<>();
//辅助队列,用于把每一层分开存储。
Queue<TreeNode> queue2 = new ArrayDeque<>();
List<List<Integer>> res = new ArrayList<>();
List<Integer> list = new ArrayList<>();
if(root == null) return res;
queue.offer(root);
while(!queue.isEmpty()){
while(!queue.isEmpty()){
TreeNode tree = queue.poll();
list.add(tree.val);
if(tree.left != null) queue2.offer(tree.left);
if(tree.right != null) queue2.offer(tree.right);
}
while(!queue2.isEmpty()){
queue.offer(queue2.poll());
}
res.add(list);
//多一个反转就OK了!
list = new ArrayList<Integer>();
}
Collections.reverse(res);
return res;
}
}
109、将有序数组转换为二叉搜索树 -简单
题目
给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 高度平衡 二叉搜索树。
高度平衡 二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不超过 1 」的二叉树。
思路:递归
由于是升序,所以树根结点就是数组中间位置的元素,即(start+end)/ 2
代码
class Solution {
public TreeNode sortedArrayToBST(int[] nums) {
return sortedArrayToBST(nums, 0, nums.length-1);
}
public TreeNode sortedArrayToBST(int[] nums, int start, int end) {
if(start > end) return null;
int index = (start + end) / 2;
TreeNode root = new TreeNode(nums[index]);
root.left = sortedArrayToBST(nums, start, index-1);
root.right = sortedArrayToBST(nums, index+1, end);
return root;
}
}
110、平衡二叉树 -简单
题目
给定一个二叉树,判断它是否是高度平衡的二叉树。
本题中,一棵高度平衡二叉树定义为:
一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。
思路:递归
和计算二叉树的深度差不多,只是在计算深度过程中如果左右子树深度差的绝对值大于2就做一下特殊处理返回
代码
class Solution {
public boolean isBalanced(TreeNode root) {
return recur(root) != -1;
}
private int recur(TreeNode root) {
if (root == null) return 0;
int left = recur(root.left);
if(left == -1) return -1;//如果是特殊值就可以终止递归返回结果了
int right = recur(root.right);
if(right == -1) return -1;//如果是特殊值就可以终止递归返回结果了
//左右子树深度差的绝对值大于2就返回一个特殊值
return Math.abs(left - right) < 2 ? Math.max(left, right) + 1 : -1;
}
}
111、二叉树最小深度 -简单
题目
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
**说明:**叶子节点是指没有子节点的节点。
思路:递归
和计算二叉树的深度差不多,但不能简单粗暴直接返回最小的那个+1,因为当一个结点的左或右子树为空时,它的深度只有 有子树一段,就像1,2,null,3,这树深度不管最大最小都是2,如果简单粗暴直接返回最小的那个+1就变为1了。
代码
class Solution {
public int minDepth(TreeNode root) {
if(root == null) return 0;
int left = minDepth(root.left);
int right = minDepth(root.right);
if(left == 0 || right == 0) return Math.max(left, right) + 1;//都为空时与一个为空时可以一起处理(0+1=1)
return Math.min(left, right) + 1;
}
}
112、路径总和 -简单
题目
给你二叉树的根节点 root 和一个表示目标和的整数 targetSum ,判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。
叶子节点 是指没有子节点的节点
思路:递归
注意:必须是根结点到 ” 叶子结点 “ 。也就是只有到叶子节点了才判断sum是否等于目标值,其它地方的结点和等于目标值不算
代码
class Solution {
public boolean hasPathSum(TreeNode root, int targetSum) {
return hasPathSum(root, targetSum, 0);
}
public boolean hasPathSum(TreeNode root, int targetSum, int sum) {
if(root == null) return false;
if(root.left == null && root.right == null){
if(root.val + sum == targetSum)
return true;
else
return false;
}else{
return hasPathSum(root.left, targetSum, sum+root.val) || hasPathSum(root.right, targetSum, sum+root.val);
}
}
}
113、路径总和2 -中等
题目
给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。
叶子节点 是指没有子节点的节点。
思路:递归
递归压栈时list在不停加元素,出栈时需要去除list对应元素
注意:必须是根结点到 ” 叶子结点 “ 。也就是只有到叶子节点了才判断sum是否等于目标值,其它地方的结点和等于目标值不算
代码
class Solution {
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
if(root == null) return res;
List list = new ArrayList<>();
pathSum(root, targetSum, 0, list);
return res;
}
public void pathSum(TreeNode root, int targetSum, int sum, List list) {
list.add(root.val);
if(root.left == null && root.right == null){
if(sum + root.val == targetSum){
res.add(new ArrayList<>(list));//不能直接添加list到res中,需要new个新的list,将值复制进去
return ;
}
}else{
sum += root.val;
}
if(root.left != null){
pathSum(root.left, targetSum, sum, list);
list.remove(list.size()-1);//如果左边为null就不需要在list中移除元素
}
if(root.right != null){
pathSum(root.right, targetSum, sum, list);
list.remove(list.size()-1);//如果右边为null就不需要在list中移除元素
}
return ;
}
}
114、二叉树展开为链表 -中等
题目
给你二叉树的根结点 root ,请你将它展开为一个单链表:
展开后的单链表应该同样使用 TreeNode ,其中 right 子指针指向链表中下一个结点,而左子指针始终为 null 。
展开后的单链表应该与二叉树 先序遍历 顺序相同。
思路:递归
根结点为root的树的单链表为:root的右边指向左子树转化的链表头结点,root左边赋值为null,然后左子树转化的链表尾结点指向root的右子树转化的链表头结点。这样就形成了递归。
代码
class Solution {
public void flatten(TreeNode root) {
dfs(root);
}
public TreeNode dfs(TreeNode root){
if(root == null) return null;
TreeNode leftHead = dfs(root.left);//得到左子树的单链表头结点
root.left = null;//右子树记得变为空
TreeNode rightNode = root.right;//把右子树先保存起
//左子树的单链表头结点如果为空就直接指向右子树的单链表头结点
if(leftHead != null){
root.right = leftHead;//指向左子树的单链表头结点
//找到左子树的单链表最后一个
while(leftHead.right != null){
leftHead = leftHead.right;
}
//将左子树的单链表最后一个指向右子树的的单链表头结点
leftHead.right = dfs(rightNode);
}else{
root.right = dfs(rightNode);
}
return root;
}
}
116、填充每个节点的下一个右侧节点指针 -中等
题目
给定一个 完美二叉树 ,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下:
struct Node {
int val;
Node *left;
Node *right;
Node *next;
}
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。
初始状态下,所有 next 指针都被设置为 NULL。
进阶:
你只能使用常量级额外空间。
使用递归解题也符合要求,本题中递归程序占用的栈空间不算做额外的空间复杂度。
思路:层序遍历
代码
class Solution {
public Node connect(Node root) {
Queue<Node> queue = new ArrayDeque<>();
Node prev = null;
if(root == null) return null;
queue.offer(root);
while(!queue.isEmpty()){
int size = queue.size();
prev = null;
for(int i = 0; i < size; i++){
Node node = queue.poll();
if(prev != null){
prev.next = node;
}
prev = node;
if(node.left != null) queue.offer(node.left);
if(node.right != null) queue.offer(node.right);
}
}
return root;
}
}
思路:满二叉树,按层迭代
从第一层(第一层只有一个节点,相当于已经串联好了)可以将第二层串联起来,第二层串联好了又可以从第二层将第三层串联起来,依次循环,tmp为当前层的一个节点,可以根据该节点将它的左右结点的next确定。
-
tmp.left.next = tmp.right;
-
tmp.right.next = tmp.next.left;
代码
class Solution {
public Node connect(Node root) {
if(root==null) {
return root;
}
Node pre = root;
/**
从第一层(第一层只有一个节点,相当于已经串联好了)可以将第二层串联起来,第二层串联好了又可以从第二层将第三层串联起来,依次循环
while(下一层不为空){
在该层已经串联好的基础上将下一层串联起来
...
走向下一层
}
*/
while(pre.left!=null) {
Node tmp = pre;
while(tmp!=null) {
tmp.left.next = tmp.right;
if(tmp.next!=null) {
tmp.right.next = tmp.next.left;
}
tmp = tmp.next;
}
//到下一层
pre = pre.left;
}
return root;
}
}
116、填充每个节点的下一个右侧节点指针 2-中等
题目
给定一个 二叉树(不是完美二叉树) ,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下:
struct Node {
int val;
Node *left;
Node *right;
Node *next;
}
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。
初始状态下,所有 next 指针都被设置为 NULL。
进阶:
你只能使用常量级额外空间。
使用递归解题也符合要求,本题中递归程序占用的栈空间不算做额外的空间复杂度。
思路:按前面一题的层序遍历一样可以,这里用上一题空间复杂度为O(1)的按层迭代的思想
与上题主要区分:在上一题中由于完美二叉树每层都是满结点的,next直接简单的指向挨着的下一个就可以;而这题结点不一定就是挨着的下一个,需要遍历找出第一个不为null的结点,代码编写比起上一题主要就多个找第一个不为null的结点的函数。
代码
class Solution {
public Node connect(Node root) {
if(root==null) {
return root;
}
Node pre = root;
while(pre != null) {
Node tmp = pre;
while(tmp!=null) {
//不是完美二叉树了需要判断下是不是空
if(tmp.left != null){
tmp.left.next = tmp.right == null ? getFirst(tmp.next):tmp.right;
}
if(tmp.right != null){
tmp.right.next = getFirst(tmp.next);
}
tmp = tmp.next;
}
//到下一层第一个结点继续循环
pre = getFirst(pre);
}
return root;
}
//得到从begin结点开始的下一层第一个不为null的结点
public Node getFirst(Node begin){
while(begin != null){
if(begin.left != null){
return begin.left;
}
if(begin.right != null){
return begin.right;
}
begin = begin.next;
}
return null;
}
}
113、求根节点到叶节点数字之和-中等
题目
给你一个二叉树的根节点 root ,树中每个节点都存放有一个 0 到 9 之间的数字。
每条从根节点到叶节点的路径都代表一个数字:
例如,从根节点到叶节点的路径 1 -> 2 -> 3 表示数字 123 。
计算从根节点到叶节点生成的 所有数字之和 。
叶节点 是指没有子节点的节点。。
思路:按113题路径求和的递归方法思想
到了根结点就把list里的数字转为一个数加到总数里
代码
class Solution {
int sum = 0;
public int sumNumbers(TreeNode root) {
if(root == null) return sum;
List list = new ArrayList<>();
pathSum(root, list);
return sum;
}
public void pathSum(TreeNode root, List list) {
if(root == null) return ;
list.add(root.val);
if(root.left == null && root.right == null){
StringBuffer num = new StringBuffer("");
for(int i = 0; i < list.size(); i++){
num.append(list.get(i)+"");
}
sum += Integer.parseInt(num.toString());
}
pathSum(root.left, list);
pathSum(root.right, list);
list.remove(list.size()-1);
return ;
}
}
思路:符合这道题的递归
一颗数的路径和等于左边的和加右边的和(不是左子树的和加右子树的和)
代码
public int sumNumbers(TreeNode root) {
return helper(root, 0);
}
public int helper(TreeNode root, int sum){
if (root == null) return 0;
int temp = sum * 10 + root.val;
if (root.left == null && root.right == null)
return temp;
return helper(root.left, temp) + helper(root.right, temp);
}
124、二叉树中的最大路径和-中等
题目
路径 被定义为一条从树中任意节点出发,沿父节点-子节点连接,达到任意节点的序列。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点,且不一定经过根节点。
路径和 是路径中各节点值的总和。
给你一个二叉树的根节点 root ,返回其 最大路径和 。
思路:递归
这个递归不同于一般的递归,其返回值并不是该递归函数意义上该返回的值(本题按函数意义讲返回的应该是该结点的最大路径和),返回的值而是为了能计算父节点的最大值,每个结点的最大值在递归过程中计算出来与全局变量max比较。
代码
class Solution {
int max = Integer.MIN_VALUE;
public int maxPathSum(TreeNode root) {
if(root == null) return 0;
maxPathSum2(root);
return max;
}
public int maxPathSum2(TreeNode root) {
if(root == null) return 0;
int leftMax = Math.max(0, maxPathSum2(root.left));
int rightMax = Math.max(0, maxPathSum2(root.right));
//递归过程中更新最大值
int temp = root.val+leftMax+rightMax;
if(max < temp) max = temp;
//返回的值是为了能计算父节点的最大值
return root.val+Math.max(leftMax, rightMax);
}
}
124、二叉搜索树迭代器-中等
题目
实现一个二叉搜索树迭代器。你将使用二叉搜索树的根节点初始化迭代器。
调用 next()
将返回二叉搜索树中的下一个最小的数。
- next() 和 hasNext() 操作的时间复杂度是 O(1)
- 并使用 O(h) 内存,其中 h 是树的高度。
思路:控制入栈
这个递归不同于一般的递归,其返回值并不是该递归函数意义上该返回的值(本题按函数意义讲返回的应该是该结点的最大路径和),返回的值而是为了能计算父节点的最大值,每个结点的最大值在递归过程中计算出来与全局变量max比较。
代码
class BSTIterator {
Stack<TreeNode> stack;
public BSTIterator(TreeNode root) {
this.stack = new Stack<TreeNode>();
this._leftmostInorder(root);
}
private void _leftmostInorder(TreeNode root) {
//走到最左结点
while (root != null) {
this.stack.push(root);
root = root.left;
}
}
public int next() {
//返回最左结点
TreeNode topmostNode = this.stack.pop();
//继续走到下一个最左结点
if (topmostNode.right != null) {
this._leftmostInorder(topmostNode.right);
}
return topmostNode.val;
}
public boolean hasNext() {
return this.stack.size() > 0;
}
}