二叉树
#二叉树理论基础篇
二叉树的递归遍历
每次写递归,都按照这三要素来写
-
确定递归函数的参数和返回值: 确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数, 并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型。
-
确定终止条件: 写完了递归算法, 运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出。
-
确定单层递归的逻辑: 确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。
1.先(根)序遍历:根左右
2.中(根)序遍历:左根右
3.后(根)序遍历:左右根
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<Integer>();
preorder(root, result);
return result;
}
public void preorder(TreeNode current, List<Integer> result){
if(current == null) return;
result.add(current.val);
preorder(current.left, result);
preorder(current.right, result);
}
}
var preorderTraversal = function(root) {
let res=[];
const dfs=function(root){
if(root===null)return ;
//先序遍历所以从父节点开始
res.push(root.val);
//递归左子树
dfs(root.left);
//递归右子树
dfs(root.right);
}
//只使用一个参数 使用闭包进行存储结果
dfs(root);
return res;
};
注意语法
二叉树的迭代遍历
1.先(根)序遍历:根左右
2.中(根)序遍历:左根右
3.后(根)序遍历:左右根
【非递归就俩类思路,要么就是循环,要么就是循环➕栈】
// 前序遍历顺序:中-左-右,入栈顺序:中-右-左
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
if (root == null){
return result;
}
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()){
TreeNode node = stack.pop();
result.add(node.val);
if (node.right != null){
stack.push(node.right);
}
if (node.left != null){
stack.push(node.left);
}
}
return result;
}
}
// 中序遍历顺序: 左-中-右 入栈顺序: 左-右
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
if (root == null){
return result;
}
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
while (cur != null || !stack.isEmpty()){
if (cur != null){
stack.push(cur);
cur = cur.left;
}else{
cur = stack.pop();
result.add(cur.val);
cur = cur.right;
}
}
return result;
}
}
// 后序遍历顺序 左-右-中 入栈顺序:中-左-右 出栈顺序:中-右-左, 最后翻转结果
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
if (root == null){
return result;
}
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()){
TreeNode node = stack.pop();
result.add(node.val);
if (node.left != null){
stack.push(node.left);
}
if (node.right != null){
stack.push(node.right);
}
}
Collections.reverse(result);
return result;
}
}
var preorderTraversal = function(root, res = []) {
if(!root) return res;
const stack = [root];
let cur = null;
while(stack.length) {
cur = stack.pop();
res.push(cur.val);
cur.right && stack.push(cur.right);
cur.left && stack.push(cur.left);
}
return res;
};
var inorderTraversal = function(root) {
let res = [];
if(root == null) return res;
let stack = [];
let cur = root;
while(cur || stack.length){
if(cur){
stack.push(cur);
cur = cur.left;
}else{
cur = stack.pop();
res.push(cur.val);
cur = cur.right;
}
}
return res;
};
var postorderTraversal = function(root) {
let res = [];
if(!root) return res;
let stack = [root];
while(stack.length){
let cur = stack.pop();
res.push(cur.val);
cur.left && stack.push(cur.left);
cur.right && stack.push(cur.right);
}
return res.reverse();
};
使用stack
二叉树层序遍历
二叉树层序遍历
层序遍历一个二叉树。就是从左到右一层一层的去遍历二叉树。这种遍历的方式和我们之前讲过的都不太一样。
需要借用一个辅助数据结构即队列来实现,队列先进先出,符合一层一层遍历的逻辑,而用栈先进后出适合模拟深度优先遍历也就是递归的逻辑。
而这种层序遍历方式就是图论中的广度优先遍历,只不过我们应用在二叉树上。
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> res = new ArrayList<List<Integer>>();
if(root == null) return res;
Queue<TreeNode> que = new LinkedList<TreeNode>();
que.offer(root);
while(!que.isEmpty()){
List<Integer> itemList = new ArrayList<Integer>();
int len = que.size();
while(len > 0){
TreeNode cur = que.poll();
itemList.add(cur.val);
if(cur.left != null) que.offer(cur.left);
if(cur.right != null) que.offer(cur.right);
len--;
}
res.add(itemList);
}
return res;
}
}
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number[][]}
*/
var levelOrder = function(root) {
let res = [];
if(root === null) return res;
let que = [];
que.push(root);
while(que.length){
let itemList = [];
let len = que.length;
while(len > 0){
let cur = que.shift();
itemList.push(cur.val);
if(cur.left != null) que.push(cur.left);
if(cur.right != null) que.push(cur.right);
len--;
}
res.push(itemList);
}
return res;
};
使用队列
二叉树层序遍历Ⅱ
把上一个的结果reverse一下
/**
* 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<List<Integer>> levelOrderBottom(TreeNode root) {
List<List<Integer>> res = new ArrayList<List<Integer>>();
if(root == null) return res;
Queue<TreeNode> que = new LinkedList<TreeNode>();
que.offer(root);
while(!que.isEmpty()){
List<Integer> itemList = new ArrayList<Integer>();
int len = que.size();
while(len > 0){
TreeNode cur = que.poll();
itemList.add(cur.val);
if(cur.left != null) que.offer(cur.left);
if(cur.right != null) que.offer(cur.right);
len--;
}
res.add(itemList);
}
List<List<Integer>> result = new ArrayList<>();
for (int i = res.size() - 1; i >= 0; i-- ) {
result.add(res.get(i));
}
return result;
}
}
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number[][]}
*/
var levelOrderBottom = function(root) {
let res = [];
if(root === null) return res;
let que = [];
que.push(root);
while(que.length){
let itemList = [];
let len = que.length;
while(len > 0){
let cur = que.shift();
itemList.push(cur.val);
if(cur.left != null) que.push(cur.left);
if(cur.right != null) que.push(cur.right);
len--;
}
res.unshift(itemList);
}
return res;
};
使用队列
js语法:可以使用unshift在前面插入。
二叉树的右视图
层序遍历的时候,判断是否遍历到单层的最后面的元素,如果是,就放进result数组中,随后返回result就可以了。
/**
* 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<Integer> rightSideView(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
if(root == null) return res;
Queue<TreeNode> que = new LinkedList<TreeNode>();
que.offer(root);
while(!que.isEmpty()){
int len = que.size();
while(len > 0){
TreeNode cur = que.poll();
if(len == 1){
res.add(cur.val);
}
if(cur.left != null) que.offer(cur.left);
if(cur.right != null) que.offer(cur.right);
len--;
}
}
return res;
}
}
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number[]}
*/
var rightSideView = function(root) {
let res = [];
if(root === null) return res;
let que = [];
que.push(root);
while(que.length){
let len = que.length;
while(len > 0){
let cur = que.shift();
if(len == 1){
res.push(cur.val);
}
if(cur.left != null) que.push(cur.left);
if(cur.right != null) que.push(cur.right);
len--;
}
}
return res;
};
使用队列
二叉树的层平均值
本题就是层序遍历的时候把一层求个总和在取一个均值。
/**
* 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<Double> averageOfLevels(TreeNode root) {
List<Double> res = new ArrayList<Double>();
if(root == null) return res;
Queue<TreeNode> que = new LinkedList<TreeNode>();
que.offer(root);
while(!que.isEmpty()){
List<Integer> itemList = new ArrayList<Integer>();
int len = que.size();
int n = len;
double sum = 0;
while(len > 0){
TreeNode cur = que.poll();
sum += cur.val;
if(cur.left != null) que.offer(cur.left);
if(cur.right != null) que.offer(cur.right);
len--;
}
res.add(sum / n);
}
return res;
}
}
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number[]}
*/
var averageOfLevels = function(root) {
let res = [];
if(root === null) return res;
let que = [];
que.push(root);
while(que.length){
let itemList = [];
let len = que.length;
let n = len;
let sum = 0;
while(len > 0){
let cur = que.shift();
sum += cur.val;
if(cur.left != null) que.push(cur.left);
if(cur.right != null) que.push(cur.right);
len--;
}
res.push(sum / n);
}
return res;
};
使用队列
java注意double
N叉树的层序遍历
本题就是层序遍历的时候把一层求个总和在取一个均值。
/*
// Definition for a Node.
class Node {
public int val;
public List<Node> children;
public Node() {}
public Node(int _val) {
val = _val;
}
public Node(int _val, List<Node> _children) {
val = _val;
children = _children;
}
};
*/
class Solution {
public List<List<Integer>> levelOrder(Node root) {
List<List<Integer>> res = new ArrayList<List<Integer>>();
if(root == null) return res;
Queue<Node> que = new LinkedList<Node>();
que.offer(root);
while(!que.isEmpty()){
List<Integer> itemList = new ArrayList<Integer>();
int len = que.size();
while(len > 0){
Node cur = que.poll();
itemList.add(cur.val);
List<Node> children = cur.children;
for(Node child : children){
if(child != null) que.offer(child);
}
len--;
}
res.add(itemList);
}
return res;
}
}
/**
* // Definition for a Node.
* function Node(val,children) {
* this.val = val;
* this.children = children;
* };
*/
/**
* @param {Node|null} root
* @return {number[][]}
*/
var levelOrder = function(root) {
let res = [];
if(root === null) return res;
let que = [];
que.push(root);
while(que.length){
let itemList = [];
let len = que.length;
while(len > 0){
let cur = que.shift();
itemList.push(cur.val);
let children = cur.children;
for(let child of children){
if(child != null) que.push(child);
}
len--;
}
res.push(itemList);
}
return res;
};
使用队列
for循环语句
在每个树行中找最大值
层序遍历,取每一层的最大值
/**
* 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<Integer> largestValues(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
if(root == null) return res;
Queue<TreeNode> que = new LinkedList<TreeNode>();
que.offer(root);
while(!que.isEmpty()){
int max = Integer.MIN_VALUE;
int len = que.size();
while(len > 0){
TreeNode cur = que.poll();
if(cur.val > max){
max = cur.val;
}
if(cur.left != null) que.offer(cur.left);
if(cur.right != null) que.offer(cur.right);
len--;
}
res.add(max);
}
return res;
}
}
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number[]}
*/
var largestValues = function(root) {
let res = [];
if(root === null) return res;
let que = [];
que.push(root);
while(que.length){
let max = -Infinity;
let len = que.length;
while(len > 0){
let cur = que.shift();
if(cur.val > max){
max = cur.val;
}
if(cur.left != null) que.push(cur.left);
if(cur.right != null) que.push(cur.right);
len--;
}
res.push(max);
}
return res;
};
使用队列
注意最小值
填充每个节点的下一个右侧节点指针
本题依然是层序遍历,只不过在单层遍历的时候记录一下本层的头部节点,然后在遍历的时候让前一个节点指向本节点就可以了
/*
// Definition for a Node.
class Node {
public int val;
public Node left;
public Node right;
public Node next;
public Node() {}
public Node(int _val) {
val = _val;
}
public Node(int _val, Node _left, Node _right, Node _next) {
val = _val;
left = _left;
right = _right;
next = _next;
}
};
*/
class Solution {
public Node connect(Node root) {
Queue<Node> tmpQueue = new LinkedList<Node>();
if (root != null) tmpQueue.add(root);
while (tmpQueue.size() != 0){
int size = tmpQueue.size();
Node cur = tmpQueue.poll();
if (cur.left != null) tmpQueue.add(cur.left);
if (cur.right != null) tmpQueue.add(cur.right);
for (int index = 1; index < size; index++){
Node next = tmpQueue.poll();
if (next.left != null) tmpQueue.add(next.left);
if (next.right != null) tmpQueue.add(next.right);
cur.next = next;
cur = next;
}
}
return root;
}
}
/**
* // Definition for a Node.
* function Node(val, left, right, next) {
* this.val = val === undefined ? null : val;
* this.left = left === undefined ? null : left;
* this.right = right === undefined ? null : right;
* this.next = next === undefined ? null : next;
* };
*/
/**
* @param {Node} root
* @return {Node}
*/
var connect = function(root) {
let que = [];
if(root != null) que.push(root);
while(que.length){
let size = que.length;
let cur = que.shift();
if(cur.left != null) que.push(cur.left);
if(cur.right != null) que.push(cur.right);
for(let i=1; i<size; i++){
let next = que.shift();
if (next.left != null) que.push(next.left);
if (next.right != null) que.push(next.right);
cur.next = next;
cur = next;
}
}
return root;
};
注意逻辑
二叉树的最大深度
使用迭代法的话,使用层序遍历是最为合适的,因为最大的深度就是二叉树的层数,和层序遍历的方式极其吻合。
在二叉树中,一层一层的来遍历二叉树,记录一下遍历的层数就是二叉树的深度,如图所示:
/**
* 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 int maxDepth(TreeNode root) {
int depth = 0;
if(root == null) return 0;
Queue<TreeNode> que = new LinkedList<TreeNode>();
que.offer(root);
while(!que.isEmpty()){
depth++;
int len = que.size();
while(len > 0){
TreeNode cur = que.poll();
if(cur.left != null) que.offer(cur.left);
if(cur.right != null) que.offer(cur.right);
len--;
}
}
return depth;
}
}
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number}
*/
var maxDepth = function (root) {
let depth = 0;
if (root === null) return depth;
let que = [root];
while (que.length) {
depth++;
let size = que.length;
while (size > 0) {
let cur = que.shift();
if (cur.left !== null) que.push(cur.left);
if (cur.right !== null) que.push(cur.right);
size--;
}
}
return depth;
};
注意逻辑
二叉树的最小深度
二叉树的最大深度 ,本题还也可以使用层序遍历的方式来解决,思路是一样的。
需要注意的是,只有当左右孩子都为空的时候,才说明遍历的最低点了。如果其中一个孩子为空则不是最低点
/**
* 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 int minDepth(TreeNode root) {
int depth = 0;
if(root == null) return depth;
Queue<TreeNode> que = new LinkedList<>();
que.offer(root);
while(!que.isEmpty()){
depth++;
int size = que.size();
while(size > 0){
TreeNode cur = que.poll();
if(cur.left == null && cur.right == null){
System.out.println(cur.val);
return depth;
}
if(cur.left != null) que.offer(cur.left);
if(cur.right != null) que.offer(cur.right);
size--;
}
}
return depth;
}
}
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number}
*/
var minDepth = function (root) {
let depth = 0;
if (root == null) return depth;
let que = [root];
while (que.length) {
depth++;
let size = que.length;
while (size > 0) {
let cur = que.shift();
if(cur.left == null && cur.right == null) return depth;
if (cur.left !== null) que.push(cur.left);
if (cur.right !== null) que.push(cur.right);
size--;
}
}
return depth;
};
注意 只有当左右孩子都为空的时候,才说明遍历的最低点了
翻转二叉树
可以发现想要翻转它,其实就把每一个节点的左右孩子交换一下就可以了。
遍历的过程中去翻转每一个节点的左右孩子就可以达到整体翻转的效果。
注意只要把每一个节点的左右孩子翻转一下,就可以达到整体翻转的效果
这道题目使用前序遍历和后序遍历都可以,唯独中序遍历不方便,因为中序遍历会把某些节点的左右孩子翻转了两次!
/**
* 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 TreeNode invertTree(TreeNode root) {
invert(root);
return root;
}
public void invert(TreeNode root){
if(root == null) return;
TreeNode temp = root.left;
root.left = root.right;
root.right = temp;
invertTree(root.left);
invertTree(root.right);
}
}
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {TreeNode}
*/
var invertTree = function (root) {
invert(root);
return root;
};
var invert = function (root) {
if (root == null) return;
let temp = root.left;
root.left = root.right;
root.right = temp;
invertTree(root.left);
invertTree(root.right);
};
使用前序后序遍历都可以
对称二叉树
本题遍历只能是“后序遍历”,因为我们要通过递归函数的返回值来判断两个子树的内侧节点和外侧节点是否相等。
正是因为要遍历两棵树而且要比较内侧和外侧节点,所以准确的来说是一个树的遍历顺序是左右中,一个树的遍历顺序是右左中。
但都可以理解算是后序遍历,尽管已经不是严格上在一个树上进行遍历的后序遍历了。
public boolean isSymmetric(TreeNode root) {
return compare(root.left, root.right);
}
public boolean compare(TreeNode left, TreeNode right){
if(left == null && right != null) return false;
if(left != null && right == null) return false;
if(left == null && right == null) return true;
if(left.val != right.val) return false;
boolean outside = compare(left.left, right.right);
boolean inside = compare(left.right, right.left);
return outside && inside;
}
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {boolean}
*/
var isSymmetric = function (root) {
return compare(root.left, root.right);
};
var compare = function(left, right) {
if (left == null && right != null) return false;
if (left != null && right == null) return false;
if (left == null && right == null) return true;
if (left.val != right.val) return false;
let outside = compare(left.left, right.right);
let inside = compare(left.right, right.left);
return outside && inside;
}
使用后序遍历
二叉树的最大深度
本题可以使用前序(中左右),也可以使用后序遍历(左右中),使用前序求的就是深度,使用后序求的是高度。
- 二叉树节点的深度:指从根节点到该节点的最长简单路径边的条数或者节点数(取决于深度从0开始还是从1开始)
- 二叉树节点的高度:指从该节点到叶子节点的最长简单路径边的条数或者节点数(取决于高度从0开始还是从1开始)
而根节点的高度就是二叉树的最大深度
class Solution {
public int maxDepth(TreeNode root) {
if(root == null) return 0;
int lheight = maxDepth(root.left);
int rheight = maxDepth(root.right);
return Math.max(lheight, rheight) + 1;
}
}
// N叉树
class Solution {
public int maxDepth(Node root) {
if (root == null)
return 0;
int depth = 0;
if (root.children != null) {
List<Node> children = root.children;
for (Node child : children) {
depth = Math.max(maxDepth(child), depth);
}
}
return depth+1;
}
}
class Solution {
public int maxDepth(Node root) {
int depth = 0;
if(root == null) return depth;
Queue<Node> que = new LinkedList<>();
que.offer(root);
while(!que.isEmpty()){
depth++;
int size = que.size();
while(size > 0){
Node cur = que.poll();
List<Node> children = cur.children;
for(Node child : children){
if(child != null) que.offer(child);
}
size--;
}
}
return depth;
}
}
var maxDepth = function (root) {
if (root == null) return 0;
let lheight = maxDepth(root.left);
let rheight = maxDepth(root.right);
return Math.max(lheight, rheight) + 1;
};
var maxDepth = function (root) {
let depth = 0;
if (root == null) return depth;
let que = [root];
while (que.length) {
depth++;
let size = que.length;
while (size > 0) {
let cur = que.shift();
let children = cur.children;
for (let child of children) {
if (child != null) que.push(child);
}
size--;
}
}
return depth;
};
使用后序遍历
二叉树的最小深度
前序遍历和后序遍历都可以,前序求的是深度,后序求的是高度。
- 二叉树节点的深度:指从根节点到该节点的最长简单路径边的条数或者节点数(取决于深度从0开始还是从1开始)
- 二叉树节点的高度:指从该节点到叶子节点的最长简单路径边的条数后者节点数(取决于高度从0开始还是从1开始)
那么使用后序遍历,其实求的是根节点到叶子节点的最小距离,就是求高度的过程,不过这个最小距离 也同样是最小深度。最小深度是从根节点到最近叶子节点的最短路径上的节点数量。注意是叶子节点。什么是叶子节点,左右孩子都为空的节点才是叶子节点
class Solution {
public int minDepth(TreeNode root) {
if (root == null) return 0;
int minleft = minDepth(root.left);
int minright = minDepth(root.right);
if(root.left == null && root.right != null)
return minright + 1;
if(root.left != null && root.right == null)
return minleft + 1;
return Math.min(minleft, minright) + 1;
}
}
var minDepth = function (root) {
if (root == null) return 0;
let minleft = minDepth(root.left);
let minright = minDepth(root.right);
if (root.left == null && root.right != null)
return minright + 1;
if (root.left != null && root.right == null)
return minleft + 1;
return Math.min(minleft, minright) + 1;
};
使用后序遍历
完全二叉树的节点个数
递归的方法用后序遍历,迭代的方法用层序遍历。
class Solution {
public int countNodes(TreeNode root) {
if(root == null) return 0;
int leftnum = countNodes(root.left);
int rightnum = countNodes(root.right);
return leftnum + rightnum + 1;
}
}
class Solution {
public int countNodes(TreeNode root) {
int count = 0;
if (root == null)
return count;
Queue<TreeNode> que = new LinkedList<>();
que.offer(root);
while (!que.isEmpty()) {
int size = que.size();
while (size > 0) {
TreeNode cur = que.poll();
count++;
if(cur.left != null) que.offer(cur.left);
if(cur.right != null) que.offer(cur.right);
size--;
}
}
return count;
}
}
var countNodes = function (root) {
if (root == null)
return 0;
let leftnum = countNodes(root.left);
let rightnum = countNodes(root.right);
return leftnum + rightnum + 1;
};
var countNodes = function (root) {
let count = 0;
if (root == null)
return count;
let que = [root];
while (que.length) {
let size = que.length;
while (size > 0) {
let cur = que.shift();
count++;
if (cur.left != null) que.push(cur.left);
if (cur.right != null) que.push(cur.right);
size--;
}
}
return count;
};
使用后序遍历或者层序遍历
平衡二叉树
递归的方法用后序遍历,迭代的方法用层序遍历。
class Solution {
public boolean isBalanced(TreeNode root) {
int height = getHeight(root);
if(height == -1) return false;
return true;
}
public int getHeight(TreeNode root){
if(root == null)
return 0;
int lheight = getHeight(root.left);
if(lheight == -1)
return -1;
int rheight = getHeight(root.right);
if(rheight == -1)
return -1;
if(Math.abs(lheight - rheight) > 1)
return -1;
return Math.max(lheight, rheight) + 1;
}
}
var isBalanced = function(root) {
var getHeight = function(root){
if(root == null)
return 0;
let lheight = getHeight(root.left);
if(lheight == -1)
return -1;
let rheight = getHeight(root.right);
if(rheight == -1)
return -1;
if(Math.abs(lheight - rheight) > 1)
return -1;
return Math.max(lheight, rheight) + 1;
}
let height = getHeight(root);
if(height == -1) return false;
return true;
};
使用后序遍历
二叉树的所有路径
递归的方法用后序遍历,迭代的方法用层序遍历。
class Solution {
public List<String> binaryTreePaths(TreeNode root) {
List<String> res = new ArrayList<String>();
List<Integer> paths = new ArrayList<Integer>();
traversal(root, paths, res);
return res;
}
private void traversal(TreeNode root, List<Integer> paths, List<String> res){
paths.add(root.val);
if(root.left == null && root.right == null){
StringBuilder sb = new StringBuilder();// StringBuilder用来拼接字符串,速度更快
for (int i = 0; i < paths.size() - 1; i++) {
sb.append(paths.get(i)).append("->");
}
sb.append(paths.get(paths.size() - 1));// 记录最后一个节点
res.add(sb.toString());// 收集一个路径
return;
}
if(root.left != null){
traversal(root.left, paths, res);
paths.remove(paths.size() - 1);
}
if(root.right != null){
traversal(root.right, paths, res);
paths.remove(paths.size() - 1);
}
}
}
var binaryTreePaths = function (root) {
let res = [];
//1. 确定递归函数 函数参数
const getPath = function (node, curPath) {
//2. 确定终止条件,到叶子节点就终止
if (node.left === null && node.right === null) {
curPath += node.val;
res.push(curPath);
return;
}
//3. 确定单层递归逻辑
curPath += node.val + '->';
node.left && getPath(node.left, curPath);
node.right && getPath(node.right, curPath);
}
getPath(root, '');
return res;
};
使用递归和回溯
左叶子之和
一层一层往上返回感觉可以用后序遍历比较方便。
class Solution {
public int sumOfLeftLeaves(TreeNode root) {
if(root == null)
return 0;
if(root.left == null && root.right == null)
return 0;
int leftnum = sumOfLeftLeaves(root.left);
if(root.left != null && root.left.left == null && root.left.right == null)
leftnum += root.left.val;
int rightnum = sumOfLeftLeaves(root.right);
return leftnum + rightnum;
}
}
var sumOfLeftLeaves = function (root) {
if (root == null)
return 0;
if (root.left == null && root.right == null)
return 0;
let leftnum = sumOfLeftLeaves(root.left);
if (root.left != null && root.left.left == null && root.left.right == null)
leftnum += root.left.val;
let rightnum = sumOfLeftLeaves(root.right);
return leftnum + rightnum;
};
使用后序遍历
找树左下角的值【好好理解】
在树的最后一行找到最左边的值。
首先要是最后一行,然后是最左边的值。
如果使用递归法,如何判断是最后一行呢,其实就是深度最大的叶子节点一定是最后一行。可以使用前序遍历(当然中序,后序都可以,因为本题没有 中间节点的处理逻辑,只要左优先就行),保证优先左边搜索,然后记录深度最大的叶子节点,此时就是树的最后一行最左边的值。
class Solution {
private int Deep = -1;
private int value = 0;
public int findBottomLeftValue(TreeNode root) {
value = root.val;
findLeftValue(root, Deep);
return value;
}
private void findLeftValue (TreeNode root,int deep) {
if (root == null) return;
if(root.left == null && root.right == null){
if(deep > Deep){
Deep = deep;
value = root.val;
}
}
if(root.left != null){
deep++;
findLeftValue(root.left, deep);
deep--;
}
if(root.right != null){
deep++;
findLeftValue(root.right, deep);
deep--;
}
}
}
class Solution {
public int findBottomLeftValue(TreeNode root) {
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
int res = 0;
while(!queue.isEmpty()){
int size = queue.size();
for(int i=0; i<size; i++){
TreeNode cur = queue.poll();
if(i==0) res = cur.val;
if(cur.left!=null) queue.offer(cur.left);
if(cur.right!= null) queue.offer(cur.right);
}
}
return res;
}
}
var findBottomLeftValue = function(root) {
//首先考虑递归遍历 前序遍历 找到最大深度的叶子节点即可
let maxPath = 0, resNode = null;
// 1. 确定递归函数的函数参数
const dfsTree = function(node, curPath) {
// 2. 确定递归函数终止条件
if(node.left === null && node.right === null) {
if(curPath > maxPath) {
maxPath = curPath;
resNode = node.val;
}
}
node.left && dfsTree(node.left, curPath+1);
node.right && dfsTree(node.right, curPath+1);
}
dfsTree(root,1);
return resNode;
};
var findBottomLeftValue = function(root) {
//考虑层序遍历 记录最后一行的第一个节点
let queue = [];
if(root === null) {
return null;
}
queue.push(root);
let resNode;
while(queue.length) {
let length = queue.length;
for(let i = 0; i < length; i++) {
let node = queue.shift();
if(i === 0) {
resNode = node.val;
}
node.left && queue.push(node.left);
node.right && queue.push(node.right);
}
}
return resNode;
};
使用后序遍历或者层次遍历
路径总和
可以使用深度优先遍历的方式(本题前中后序都可以,无所谓,因为中节点也没有处理逻辑)来遍历二叉树
1.确定递归函数的参数和返回类型
参数:需要二叉树的根节点,还需要一个计数器,这个计数器用来计算二叉树的一条边之和是否正好是目标和,计数器为int型。
再来看返回值,递归函数什么时候需要返回值?什么时候不需要返回值?这里总结如下三点:
- 如果需要搜索整棵二叉树且不用处理递归返回值,递归函数就不要返回值。(这种情况就是本文下半部分介绍的113.路径总和ii)
- 如果需要搜索整棵二叉树且需要处理递归返回值,递归函数就需要返回值。 (这种情况我们在236. 二叉树的最近公共祖先 (opens new window)中介绍)
- 如果要搜索其中一条符合条件的路径,那么递归一定需要返回值,因为遇到符合条件的路径了就要及时返回。(本题的情况)
遍历的路线,并不要遍历整棵树,所以递归函数需要返回值,可以用bool类型表示。
2.确定终止条件
首先计数器如何统计这一条路径的和呢?
不要去累加然后判断是否等于目标和,那么代码比较麻烦,可以用递减,让计数器count初始为目标和,然后每次减去遍历路径节点上的数值。
如果最后count == 0,同时到了叶子节点的话,说明找到了目标和。
如果遍历到了叶子节点,count不为0,就是没找到。
3.确定单层递归的逻辑
因为终止条件是判断叶子节点,所以递归的过程中就不要让空节点进入递归了。
递归函数是有返回值的,如果递归函数返回true,说明找到了合适的路径,应该立刻返回。
class Solution {
public boolean hasPathSum(TreeNode root, int targetSum) {
if (root == null) {
return false;
}
if(root.left == null && root.right == null){
if(targetSum - root.val == 0)
return true;
}
if(root.left != null){
targetSum -= root.val;
if(hasPathSum(root.left, targetSum))
return true;
targetSum += root.val;
}
if(root.right != null){
targetSum -= root.val;
if(hasPathSum(root.right, targetSum))
return true;
targetSum += root.val;
}
return false;
}
}
class Solution {
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
List<List<Integer>> res = new ArrayList<>();
if (root == null) return res; // 非空判断
List<Integer> path = new LinkedList<>();
preorderdfs(root, targetSum, res, path);
return res;
}
public void preorderdfs(TreeNode root, int targetSum, List<List<Integer>> res, List<Integer> path) {
path.add(root.val);
// 遇到了叶子节点
if (root.left == null && root.right == null) {
// 找到了和为 targetsum 的路径
if (targetSum - root.val == 0) {
res.add(new ArrayList<>(path));
}
return; // 如果和不为 targetsum,返回
}
if (root.left != null) {
preorderdfs(root.left, targetSum - root.val, res, path);
path.remove(path.size() - 1); // 回溯
}
if (root.right != null) {
preorderdfs(root.right, targetSum - root.val, res, path);
path.remove(path.size() - 1); // 回溯
}
}
}
var hasPathSum = function(root, targetSum) {
if(root == null) return false;
if(root.left == null && root.right == null){
if(targetSum - root.val == 0 ){
return true;
}
}
if(root.left != null){
targetSum -= root.val;
if(hasPathSum(root.left, targetSum))
return true;
targetSum += root.val;
}
if(root.right != null){
targetSum -= root.val;
if(hasPathSum(root.right, targetSum))
return true;
targetSum += root.val;
}
return false;
};
var pathSum = function(root, targetSum) {
let res = [];
if(root == null) return res;
let path = [];
var preorder = function(root, targetSum){
path.push(root.val);
if(root.left == null && root.right == null){
if(targetSum - root.val == 0){
res.push([...path]);
}
return;
}
if(root.left != null){
preorder(root.left, targetSum-root.val);
path.pop();
}
if(root.right != null){
preorder(root.right, targetSum-root.val);
path.pop();
}
}
preorder(root, targetSum);
return res;
};
java语法:res.add(new ArrayList<>(path));
js语法:res.add([...path]);
从中序与后序遍历序列构造二叉树
说到一层一层切割,就应该想到了递归。
来看一下一共分几步:
-
第一步:如果数组大小为零的话,说明是空节点了。
-
第二步:如果不为空,那么取后序数组最后一个元素作为节点元素。
-
第三步:找到后序数组最后一个元素在中序数组的位置,作为切割点
-
第四步:切割中序数组,切成中序左数组和中序右数组 (顺序别搞反了,一定是先切中序数组)
-
第五步:切割后序数组,切成后序左数组和后序右数组
-
第六步:递归处理左区间和右区间
在切割的过程中会产生四个区间,把握不好不变量的话,一会左闭右开,一会左闭右闭,必然乱套!
此时有一个很重的点,就是中序数组大小一定是和后序数组的大小相同的(这是必然)。
class Solution {
public TreeNode buildTree(int[] inorder, int[] postorder) {
if(postorder.length == 0 || inorder.length == 0)
return null;
return buildHelper(inorder, 0, inorder.length, postorder, 0, postorder.length);
}
private TreeNode buildHelper(int[] inorder, int inorderStart, int inorderEnd, int[] postorder, int postorderStart, int postorderEnd){
if(postorderStart == postorderEnd)
return null;
int rootVal = postorder[postorderEnd - 1];
TreeNode root = new TreeNode(rootVal);
int middleIndex;
for (middleIndex = inorderStart; middleIndex < inorderEnd; middleIndex++){
if(inorder[middleIndex] == rootVal)
break;
}
int leftInorderStart = inorderStart;
int leftInorderEnd = middleIndex;
int rightInorderStart = middleIndex + 1;
int rightInorderEnd = inorderEnd;
int leftPostorderStart = postorderStart;
int leftPostorderEnd = postorderStart + (middleIndex - inorderStart);
int rightPostorderStart = leftPostorderEnd;
int rightPostorderEnd = postorderEnd - 1;
root.left = buildHelper(inorder, leftInorderStart, leftInorderEnd, postorder, leftPostorderStart, leftPostorderEnd);
root.right = buildHelper(inorder, rightInorderStart, rightInorderEnd, postorder, rightPostorderStart, rightPostorderEnd);
return root;
}
}
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {number[]} inorder
* @param {number[]} postorder
* @return {TreeNode}
*/
var buildTree = function (inorder, postorder) {
if (!postorder.length) return null;
const rootVal = postorder.pop(); // 从后序遍历的数组中获取中间节点的值, 即数组最后一个值
let rootIndex = inorder.indexOf(rootVal); // 获取中间节点在中序遍历中的下标
const root = new TreeNode(rootVal); // 创建中间节点
root.left = buildTree(inorder.slice(0, rootIndex), postorder.slice(0, rootIndex)); // 创建左节点
root.right = buildTree(inorder.slice(rootIndex + 1), postorder.slice(rootIndex)); // 创建右节点
return root;
};
注意逻辑和迭代的区间
最大二叉树
构造树一般采用的是前序遍历,因为先构造中间节点,然后递归构造左子树和右子树。
- 确定递归函数的参数和返回值
参数传入的是存放元素的数组,返回该数组构造的二叉树的头结点,返回类型是指向节点的指针。
- 确定终止条件
题目中说了输入的数组大小一定是大于等于1的,所以我们不用考虑小于1的情况,那么当递归遍历的时候,如果传入的数组大小为1,说明遍历到了叶子节点了。
那么应该定义一个新的节点,并把这个数组的数值赋给新的节点,然后返回这个节点。 这表示一个数组大小是1的时候,构造了一个新的节点,并返回。
- 确定单层递归的逻辑
这里有三步工作
- 先要找到数组中最大的值和对应的下标, 最大的值构造根节点,下标用来下一步分割数组。
- 最大值所在的下标左区间 构造左子树
- 最大值所在的下标右区间 构造右子树
class Solution {
public TreeNode constructMaximumBinaryTree(int[] nums) {
return construct(nums, 0, nums.length);
}
public TreeNode construct(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 = construct(nums, leftIndex, maxIndex);
root.right = construct(nums, maxIndex + 1, rightIndex);
return root;
}
}
var constructMaximumBinaryTree = function (nums) {
const BuildTree = (arr, left, right) => {
if (left > right)
return null;
let maxValue = -1;
let maxIndex = -1;
for (let i = left; i <= right; ++i) {
if (arr[i] > maxValue) {
maxValue = arr[i];
maxIndex = i;
}
}
let root = new TreeNode(maxValue);
root.left = BuildTree(arr, left, maxIndex - 1);
root.right = BuildTree(arr, maxIndex + 1, right);
return root;
}
let root = BuildTree(nums, 0, nums.length - 1);
return root;
};
注意逻辑和迭代的区间
合并二叉树
1.确定递归函数的参数和返回值:
首先要合入两个二叉树,那么参数至少是要传入两个二叉树的根节点,返回值就是合并之后二叉树的根节点。
2.确定终止条件:
因为是传入了两个树,那么就有两个树遍历的节点t1 和 t2,如果t1 == NULL 了,两个树合并就应该是 t2 了(如果t2也为NULL也无所谓,合并之后就是NULL)。
反过来如果t2 == NULL,那么两个数合并就是t1(如果t1也为NULL也无所谓,合并之后就是NULL)。
3.确定单层递归的逻辑:
单层递归的逻辑就比较好写了,这里我们重复利用一下t1这个树,t1就是合并之后树的根节点(就是修改了原来树的结构)。
那么单层递归中,就要把两棵树的元素加到一起。
接下来t1 的左子树是:合并 t1左子树 t2左子树之后的左子树。
t1 的右子树:是 合并 t1右子树 t2右子树之后的右子树。
最终t1就是合并之后的根节点。
class Solution {
public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
if(root1 == null) return root2;
if(root2 == null) return root1;
root1.val += root2.val;
root1.left = mergeTrees(root1.left, root2.left);
root1.right = mergeTrees(root1.right, root2.right);
return root1;
}
}
var mergeTrees = function (root1, root2) {
if (root1 == null) return root2;
if (root2 == null) return root1;
root1.val += root2.val;
root1.left = mergeTrees(root1.left, root2.left);
root1.right = mergeTrees(root1.right, root2.right);
return root1;
};
可不用构造新树
二叉搜索树中的搜索
二叉搜索树是一个有序树:
- 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 它的左、右子树也分别为二叉搜索树
class Solution {
public TreeNode searchBST(TreeNode root, int val) {
if(root == null || root.val == val) return root;
TreeNode result = null;
if(val < root.val){
result = searchBST(root.left, val);
}else if(val > root.val){
result = searchBST(root.right, val);
}
return result;
}
}
class Solution {
public TreeNode searchBST(TreeNode root, int val) {
if(root == null || root.val == val) return root;
TreeNode cur=root;
while(cur != null){
if(val < cur.val) cur = cur.left;
else if(val > cur.val) cur = cur.right;
else return cur;
}
return cur;
}
}
var searchBST = function(root, val) {
if(root == null || root.val == val) return root;
let result = null;
if(val < root.val) result = searchBST(root.left, val);
else if(val > root.val) result = searchBST(root.right, val);
return result;
};
var searchBST = function(root, val) {
// if(root == null || root.val == val) return root;
let cur = root;
while(cur != null){
if(val < cur.val) cur = cur.left;
else if(val > cur.val) cur = cur.right;
else return cur;
}
return null;
};
...
验证二叉搜索树【好好理解】
要知道中序遍历下,输出的二叉搜索树节点的数值是有序序列。
有了这个特性,验证二叉搜索树,就相当于变成了判断一个序列是不是递增的了。
class Solution {
TreeNode max;
public boolean isValidBST(TreeNode root) {
if (root == null) {
return true;
}
// 左
boolean left = isValidBST(root.left);
if (!left) {
return false;
}
// 中
if (max != null && root.val <= max.val) {
return false;
}
max = root;
// 右
boolean right = isValidBST(root.right);
return right;
}
}
var isValidBST = function(root) {
let pre = null;
var inOrder = function(root){
if(root === null) return true;
let isleft = inOrder(root.left);
if(pre !== null && pre.val >= root.val){
return false;
}
pre = root;
let isRight = inOrder(root.right);
return isleft && isRight;
}
return inOrder(root);
};
。。。
二叉搜索树的最小绝对差
在一个有序数组上求两个数最小差值,这是不是就是一道送分题了。
最直观的想法,就是把二叉搜索树转换成有序数组,然后遍历一遍数组,就统计出来最小差值了。
class Solution {
TreeNode pre = null;
int result = Integer.MAX_VALUE;;
public int getMinimumDifference(TreeNode root) {
if(root == null) return 0;
traversal(root);
return result;
}
public void traversal(TreeNode root){
if(root == null) return;
traversal(root.left);
if(pre != null){
result = Math.min(result, root.val - pre.val);
}
pre = root;
traversal(root.right);
}
}
var getMinimumDifference = function(root) {
if(root === null) return 0;
let pre = null;
let result = Infinity;
var inOrder = function(root){
if(root === null) return;
inOrder(root.left);
if(pre !== null){
result = Math.min(result, root.val - pre.val);
}
pre = root;
inOrder(root.right);
}
inOrder(root);
return result;
};
中序遍历为从小到大的有序数组,所以只需要计算相邻两个的差值。
二叉搜索树中的众数
如果不是二叉搜索树,最直观的方法一定是把这个树都遍历了,用map统计频率,把频率排个序,最后取前面高频的元素的集合。
既然是搜索树,它中序遍历就是有序的。遍历有序数组的元素出现频率,从头遍历,那么一定是相邻两个元素作比较,然后就把出现频率最高的元素输出就可以了。
class Solution {
ArrayList<Integer> resList;
int maxCount;
int count;
TreeNode pre;
public int[] findMode(TreeNode root) {
resList = new ArrayList<Integer>();
maxCount =0;
count = 0;
pre = null;
traversal(root);
int[] res = new int[resList.size()];
for (int i = 0; i < resList.size(); i++) {
res[i] = resList.get(i);
}
return res;
}
public void traversal(TreeNode root){
if(root == null) return;
traversal(root.left);
int rootVal = root.val;
if(pre == null || rootVal != pre.val){
count = 1;
} else {
count++;
}
if(count > maxCount){
resList.clear();
resList.add(rootVal);
maxCount = count;
}else if(count == maxCount){
resList.add(rootVal);
}
pre = root;
traversal(root.right);
}
}
var findMode = function(root) {
// 不使用额外空间,使用中序遍历,设置出现最大次数初始值为1
let count = 0,maxCount = 1;
let pre = root,res = [];
// 1.确定递归函数及函数参数
const travelTree = function(cur) {
// 2. 确定递归终止条件
if(cur === null) {
return ;
}
travelTree(cur.left);
// 3. 单层递归逻辑
if(pre.val === cur.val) {
count++;
}else {
count = 1;
}
pre = cur;
if(count === maxCount) {
res.push(cur.val);
}
if(count > maxCount) {
res = [];
maxCount = count;
res.push(cur.val);
}
travelTree(cur.right);
}
travelTree(root);
return res;
};
注意js语法
二叉树的最近公共祖先
首先最容易想到的一个情况:如果找到一个节点,发现左子树出现结点p,右子树出现节点q,或者 左子树出现结点q,右子树出现节点p,那么该节点就是节点p和q的最近公共祖先。如果递归遍历遇到q,就将q返回,遇到p 就将p返回,那么如果 左右子树的返回值都不为空,说明此时的中节点,一定是q 和p 的最近祖先。
二叉树节点数值是不重复的,而且一定存在 q 和 p。
但是很多人容易忽略一个情况,就是节点本身p(q),它拥有一个子孙节点q(p)。其实情况一 和 情况二 代码实现过程都是一样的,也可以说,实现情况一的逻辑,顺便包含了情况二。因为遇到 q 或者 p 就返回,这样也包含了 q 或者 p 本身就是 公共祖先的情况。
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root == null) return root;
if(root == p || root == q) return root;
TreeNode left = lowestCommonAncestor(root.left, p, q);
TreeNode right = lowestCommonAncestor(root.right, p, q);
if(left != null && right != null) return root;
if(left != null && right == null) return left;
if(left == null && right != null) return right;
return null;
}
}
var lowestCommonAncestor = function (root, p, q) {
if (root == null) return root;
if (root == p || root == q) return root;
let left = lowestCommonAncestor(root.left, p, q);
let right = lowestCommonAncestor(root.right, p, q);
if (left != null && right != null) return root;
if (left != null && right == null) return left;
if (left == null && right != null) return right;
return null;
};
二叉搜索树的最近公共祖先
在有序树里,如果判断一个节点的左子树里有p,右子树里有q呢?
因为是有序树,所以 如果 中间节点是 q 和 p 的公共祖先,那么 中节点的数组 一定是在 [p, q]区间的。即 中节点 > p && 中节点 < q 或者 中节点 > q && 中节点 < p。
那么只要从上到下去遍历,遇到 cur节点是数值在[p, q]区间中则一定可以说明该节点cur就是p 和 q的公共祖先。 一定是最近公共祖先
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
// if(root == null) return root;
// TreeNode
int rootVal = root.val;
if(rootVal > p.val && rootVal > q.val){
return lowestCommonAncestor(root.left, p, q);
}else if(rootVal < p.val && rootVal < q.val){
return lowestCommonAncestor(root.right, p, q);
}
return root;
}
}
var lowestCommonAncestor = function(root, p, q) {
if(root.val > p.val && root.val > q.val) return lowestCommonAncestor(root.left, p, q);
if(root.val < p.val && root.val < q.val) return lowestCommonAncestor(root.right, p, q);
return root;
};
两个节点是一定存在的,所以不需要判断Null
二叉搜索树中的插入操作
其实可以不考虑题目中提示所说的改变树的结构的插入方式。
如下演示视频中可以看出:只要按照二叉搜索树的规则去遍历,遇到空节点就插入节点就可以了。
需要调整二叉树的结构么? 并不需要。。
只要遍历二叉搜索树,找到空节点 插入元素就可以了
class Solution {
public TreeNode insertIntoBST(TreeNode root, int val) {
if(root == null){
TreeNode node = new TreeNode(val);
return node;
}
if(val < root.val){
TreeNode left = insertIntoBST(root.left, val);
root.left = left;
}
if(val > root.val){
TreeNode right = insertIntoBST(root.right, val);
root.right = right;
}
return root;
}
}
var insertIntoBST = function (root, val) {
if (root == null) {
let node = new TreeNode(val);
return node;
}
if (val < root.val) {
let left = insertIntoBST(root.left, val);
root.left = left;
}
if (val > root.val) {
let right = insertIntoBST(root.right, val);
root.right = right;
}
return root;
};
删除二叉搜索树中的节点
一般来说,删除节点可分为两个步骤:
首先找到需要删除的节点; 如果找到了,删除它。
1.确定递归函数参数以及返回值
这里也可以通过递归返回值删除节点。
2.确定终止条件
遇到空返回,其实这也说明没找到删除的节点,遍历到空节点直接返回了
3.确定单层递归的逻辑
这里就把二叉搜索树中删除节点遇到的情况都搞清楚。
有以下五种情况:
- 第一种情况:没找到删除的节点,遍历到空节点直接返回了
- 找到删除的节点
- 第二种情况:左右孩子都为空(叶子节点),直接删除节点, 返回NULL为根节点
- 第三种情况:删除节点的左孩子为空,右孩子不为空,删除节点,右孩子补位,返回右孩子为根节点
- 第四种情况:删除节点的右孩子为空,左孩子不为空,删除节点,左孩子补位,返回左孩子为根节点
- 第五种情况:左右孩子节点都不为空,则将删除节点的左子树头结点(左孩子)放到删除节点的右子树的最左面节点的左孩子上,返回删除节点右孩子为新的根节点。
class Solution {
public TreeNode deleteNode(TreeNode root, int key) {
if(root == null) return null;
if(root.val == key){
if(root.left == null && root.right == null)
return null;
else if(root.left != null && root.right == null){
return root.left;
}else if(root.left == null && root.right != null){
return root.right;
}else{
TreeNode cur = root.right;
while(cur.left != null){
cur = cur.left;
}
cur.left = root.left;
return root.right;
}
}
TreeNode left = deleteNode(root.left, key);
TreeNode right = deleteNode(root.right, key);
root.left = left;
root.right = right;
return root;
}
}
var deleteNode = function (root, key) {
if (root == null) return null;
if (root.val == key) {
if (root.left == null && root.right == null)
return null;
else if (root.left != null && root.right == null) {
return root.left;
} else if (root.left == null && root.right != null) {
return root.right;
} else {
let cur = root.right;
while (cur.left != null) {
cur = cur.left;
}
cur.left = root.left;
return root.right;
}
}
let left = deleteNode(root.left, key);
let right = deleteNode(root.right, key);
root.left = left;
root.right = right;
return root;
};
修剪二叉搜索树
递归三部曲:
1.确定递归函数的参数以及返回值
因为是要遍历整棵树,做修改,其实不需要返回值也可以,我们也可以完成修剪(其实就是从二叉树中移除节点)的操作。
但是有返回值,更方便,可以通过递归函数的返回值来移除节点。
2.确定终止条件
修剪的操作并不是在终止条件上进行的,所以就是遇到空节点返回就可以了。
3.确定单层递归的逻辑
如果root(当前节点)的元素小于low的数值,那么应该递归右子树,并返回右子树符合条件的头结点。
如果root(当前节点)的元素大于high的,那么应该递归左子树,并返回左子树符合条件的头结点。
接下来要将下一层处理完左子树的结果赋给root->left,处理完右子树的结果赋给root->right。
最后返回root节点
class Solution {
public TreeNode trimBST(TreeNode root, int low, int high) {
if(root == null) return null;
if(root.val < low){
return trimBST(root.right, low, high);
}else if(root.val > high){
return trimBST(root.left, low, high);
}
TreeNode left = trimBST(root.left, low, high);
TreeNode right = trimBST(root.right, low, high);
root.left = left;
root.right = right;
return root;
}
}
var trimBST = function (root, low, high) {
if (root == null) return null;
if (root.val < low) {
return trimBST(root.right, low, high);
} else if (root.val > high) {
return trimBST(root.left, low, high);
}
let left = trimBST(root.left, low, high);
let right = trimBST(root.right, low, high);
root.left = left;
root.right = right;
return root;
};
即使是符合条件,它的右子树中也可能有不符合的状况。
将有序数组转换为二叉搜索树
题目中说要转换为一棵高度平衡二叉搜索树。因为只要给我们一个有序数组,如果不强调平衡,都可以以线性结构来构造二叉搜索树。
1.确定递归函数返回值及其参数
删除二叉树节点,增加二叉树节点,都是用递归函数的返回值来完成,这样是比较方便的。那么本题要构造二叉树,依然用递归函数的返回值来构造中节点的左右孩子。
再来看参数,首先是传入数组,然后就是左下标left和右下标right,在构造二叉树的时候尽量不要重新定义左右区间数组,而是用下标来操作原数组。
这里注意,我这里定义的是左闭右闭区间,在不断分割的过程中,也会坚持左闭右闭的区间,这又涉及到我们讲过的循环不变量。
2.确定递归终止条件
这里定义的是左闭右闭的区间,所以当区间 left > right的时候,就是空节点了。
3.确定单层递归的逻辑
首先取数组中间元素的位置,不难写出int mid = (left + right) / 2;
,这么写其实有一个问题,就是数值越界,例如left和right都是最大int,这么操作就越界了,在二分法 (opens new window)中尤其需要注意!
所以可以这么写:int mid = left + ((right - left) / 2);
接着划分区间,root的左孩子接住下一层左区间的构造节点,右孩子接住下一层右区间构造的节点。
class Solution {
public TreeNode sortedArrayToBST(int[] nums) {
return construct(nums, 0, nums.length-1);
}
public TreeNode construct(int[] nums, int left, int right){
if(left > right) return null;
int mid = (left + right)/2;
TreeNode root = new TreeNode(nums[mid]);
TreeNode leftNode = construct(nums, left, mid-1);
TreeNode rightNode = construct(nums, mid+1, right);
root.left = leftNode;
root.right = rightNode;
return root;
}
}
var sortedArrayToBST = function (nums) {
var construct = function(nums, left, right){
if(left > right) return null;
let mid = Math.floor(left + (right - left) / 2);
let root = new TreeNode(nums[mid]);
let leftNode = construct(nums, left, mid-1);
let rightNode = construct(nums, mid+1, right);
root.left = leftNode;
root.right = rightNode;
return root;
}
return construct(nums, 0, nums.length-1);
};
int mid = left + ((right - left) / 2);不会越界
把二叉搜索树转换为累加树
从树中可以看出累加的顺序是右中左,所以我们需要反中序遍历这个二叉树,然后顺序累加就可以了。
class Solution {
int pre=0;
public TreeNode convertBST(TreeNode root) {
update(root);
return root;
}
public void update(TreeNode root){
if(root == null) return;
update(root.right);
root.val += pre;
pre = root.val;
update(root.left);
}
}
var convertBST = function(root) {
let pre = 0;
var update = function(root) {
if(root == null) return;
update(root.right);
root.val += pre;
pre = root.val;
update(root.left);
}
update(root);
return root;
};
遍历顺序为右左中。