二叉树的层序遍历
测试链接 : . - 力扣(LeetCode)
class Solution {
public static TreeNode[] queue = new TreeNode[2001];
public List<List<Integer>> levelOrder(TreeNode root) {
// 利用队列
int l, r;
List<List<Integer>> ans = new ArrayList<>();
if (root != null) {
l = r = 0;
queue[r++] = root;
while (l < r) { // 队列里还有东西
int size = r - l;
List<Integer> list = new ArrayList<>();
for (int i = 0; i < size; i++) {
TreeNode cur = queue[l++];
list.add(cur.val);
if (cur.left != null) {
queue[r++] = cur.left;
}
if (cur.right != null) {
queue[r++] = cur.right;
}
}
ans.add(list);
}
}
return ans;
}
}
二叉树的锯齿形层序遍历
测试链接 : . - 力扣(LeetCode)
与层序遍历的区别就是多了一个调头的操作
class Solution {
public static TreeNode[] queue = new TreeNode[2001];
public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
List<List<Integer>> ans = new ArrayList<>();
int l, r;
if (root != null) {
l = r = 0;
queue[r++] = root;
// false 左到右
// true 右到左
boolean reverse = false;
while (l < r) {
List<Integer> list = new ArrayList<>();
int size = r - l;
// 左到右 l.....r-1
// 右到左 r-1...l
for (int i = reverse ? r - 1 : l, j = reverse ? -1 : 1, k = 0; k < size; k++, i += j) {
list.add(queue[i].val);
}
for (int i = 0; i < size; i++) {
TreeNode cur = queue[l++];
if (cur.left != null)
queue[r++] = cur.left;
if (cur.right != null)
queue[r++] = cur.right;
}
ans.add(list);
reverse = !reverse;
}
}
return ans;
}
}
二叉树的最大特殊宽度
测试链接 : . - 力扣(LeetCode)
每一层的 宽度 被定义为该层最左和最右的非空节点(即,两个端点)之间的长度。将这个二叉树视作与满二叉树结构相同,两端点间会出现一些延伸到这一层的 null 节点,这些 null 节点也计入长度。
class Solution {
public static TreeNode[] treeQueue = new TreeNode[3001];
public static int[] indexQueue = new int[3001];
public int widthOfBinaryTree(TreeNode root) {
// 记录下对应的坐标就能很好的求每一层的宽度
int l, r;
int max = 0;
if (root != null) {
l = r = 0;
treeQueue[r] = root;
indexQueue[r++] = 1;
while (l < r) {
int size = r - l;
// 取每一层的第一个和最后一个值
// l.....r-1
max = Math.max(max, indexQueue[r - 1] - indexQueue[l] + 1);
for (int i = 0; i < size; i++) {
TreeNode cur = treeQueue[l];
int index = indexQueue[l++];
if (cur.left != null) {
treeQueue[r] = cur.left;
indexQueue[r++] = index * 2;
}
if (cur.right != null) {
treeQueue[r] = cur.right;
indexQueue[r++] = index * 2 + 1;
}
}
}
}
return max;
}
}
求二叉树的最大、最小深度
最大深度 : . - 力扣(LeetCode)
class Solution {
public int maxDepth(TreeNode root) {
//自底向上数所以要算上自身 +1
return root == null ? 0 : Math.max(maxDepth(root.left),maxDepth(root.right))+1;
}
}
最小深度 : . - 力扣(LeetCode)
class Solution {
public int minDepth(TreeNode root) {
//自底向上
//先考虑最底部会出现什么情况
if(root == null) return 0;
if(root.left ==null && root.right == null) return 1;
int ldeep = Integer.MAX_VALUE;
int rdeep = Integer.MAX_VALUE;
if(root.left != null) ldeep = minDepth(root.left);
if(root.right !=null) rdeep = minDepth(root.right);
return Math.min(ldeep,rdeep)+1;
}
}
二叉树先序序列化和反序列化
测试链接 : . - 力扣(LeetCode)
// 二叉树可以通过先序、后序或者按层遍历的方式序列化和反序列化
// 但是,二叉树无法通过中序遍历的方式实现序列化和反序列化
// 因为不同的两棵树,可能得到同样的中序序列,即便补了空位置也可能一样。
// 比如如下两棵树
// __2
// /
// 1
// 和
// 1__
// \
// 2
// 补足空位置的中序遍历结果都是{ null, 1, null, 2, null}
public class Codec {
//序列化
public String serialize(TreeNode root) {
StringBuilder builder = new StringBuilder();
s(root, builder);
return builder.toString();
}
public void s(TreeNode root, StringBuilder builder) {
if (root == null) {
builder.append("#,");
} else {
builder.append(root.val + ",");
s(root.left, builder);
s(root.right, builder);
}
}
//反序列化
public TreeNode deserialize(String data) {
String[] vals = data.split(",");
cnt=0;
return g(vals);
}
// 当前数组消费到哪了
public static int cnt;
TreeNode g(String[] vals) {
//自顶向下
String cur = vals[cnt++];
if (cur.equals("#")) {
return null;
} else {
TreeNode head = new TreeNode(Integer.valueOf(cur));
head.left = g(vals);
head.right = g(vals);
return head;
}
}
}
二叉树按层序列化和反序列化
测试链接 : . - 力扣(LeetCode)
public class Codec {
// 层序遍历需要队列
// 其实也就是让你去层序遍历一下二叉树并记录
public static int MAX = 10001;
public static TreeNode[] queue = new TreeNode[MAX];
public String serialize(TreeNode root) {
int l, r;
StringBuilder builder = new StringBuilder();
if (root != null) {
l = r = 0;
builder.append(root.val + ",");
queue[r++] = root;
while (l < r) {
// 取出队列第一个元素也就是本层第一个元素
root = queue[l++];
// 这道题不用再分一层一层,就是求个size用for循环,拿到数据直接将左右子树放入队列就行
if (root.left != null) {
builder.append(root.left.val + ",");
queue[r++] = root.left;
} else {
// 统一规定 空值用#表示
builder.append("#,");
}
if (root.right != null) {
builder.append(root.right.val + ",");
queue[r++] = root.right;
} else {
builder.append("#,");
}
}
}
return builder.toString();
}
// Decodes your encoded data to tree.
public TreeNode deserialize(String data) {
if (data.equals("")) {
return null;
}
String[] s = data.split(",");
int l = 0, r = 0;
int index = 0;
//队列的作用是承载有值的节点
//先取出第一个值并放入队列中
TreeNode root = generate(s[index++]);
queue[r++] = root;
while(l<r){
TreeNode cur = queue[l++];
cur.left = generate(s[index++]);
if(cur.left!=null){
queue[r++] = cur.left;
}
cur.right = generate(s[index++]);
if(cur.right!=null){
queue[r++] = cur.right;
}
}
return root;
}
public TreeNode generate(String s){
return s.equals("#") ? null : new TreeNode(Integer.valueOf(s));
}
利用先序与中序遍历序列构造二叉树
测试链接 : . - 力扣(LeetCode)
class Solution {
public TreeNode buildTree(int[] preorder, int[] inorder) {
// 先序第一个节点一定是头
// 根据中序划分出范围 左边的都是左子树 右边的都是右子树
if (preorder == null || inorder == null || preorder.length != inorder.length) {
return null;
}
HashMap<Integer, Integer> map = new HashMap<>();
//快速的找到中序序列中值对应的坐标
//不可能每次去找中序位置的时候都遍历一遍数组,这里就想到用hashmap先记录下来
for (int i = 0; i < inorder.length; i++) {
map.put(inorder[i], i);
}
return build(preorder, 0, preorder.length - 1, inorder, 0, inorder.length - 1, map);
}
public static TreeNode build(int[] preorder, int l1, int r1, int[] inorder, int l2, int r2,
HashMap<Integer, Integer> map) {
//自顶向下 返回节点 再连接左右子树
if (l1 > r1) {
return null;
}
TreeNode node = new TreeNode(preorder[l1]);
if (r1 == l1) {
return node;
}
int k = map.get(preorder[l1]);
// l1.....).....r1
// l2....k.....r2
node.left = build(preorder, l1 + 1, l1 + k - l2, inorder, l2, k - 1, map);
node.right = build(preorder, l1+k-l2+1, r1, inorder, k + 1, r2, map);
return node;
}
}
后序与中序遍历与这个类似,最后一个一定是头节点,然后再去中序找左右子树
验证完全二叉树
测试链接 : . - 力扣(LeetCode)
class Solution {
//进行层序遍历,需要队列
public static int MAX = 101;
public static TreeNode queue[] = new TreeNode[MAX];
public static int l,r;
public boolean isCompleteTree(TreeNode root) {
if(root == null){
return true;
}
l = r = 0;
queue[r++] = root;
//碰到了叶子节点或者只有左子树的节点了,之后的节点都必须是叶子节点
boolean isleaf = false;
while(l < r){
root = queue[l++];
//1、如果只有右子树直接返回false
//2、如果前面有过不双全的节点了(叶子节点或者只有左子树)之后的都必须是叶子节点
if((root.left==null&&root.right!=null) || (isleaf&&(root.left!=null||root.right!=null))){
return false;
}
if(root.left!=null){
queue[r++] = root.left;
}
if(root.right!=null){
queue[r++] = root.right;
}
//碰到叶子节点或者只有左子树的节点记录下来
//root.left == null && root.right == null
//root.left != null && root.right ==null
//合并一下只要右子树是null就记录
if(root.right==null){
isleaf = true;
}
}
return true;
}
}
求完全二叉树的节点个数
测试链接 : . - 力扣(LeetCode)
完全二叉树:
- 除了最底层以外,其余的每一层节点数都是满的
首先需要明确完全二叉树的定义:它是一棵空树或者它的叶子节点只出在最后两层,若最后一层不满则叶子节点只在最左侧。
再来回顾一下满二叉的节点个数怎么计算,如果满二叉树的层数为h,则总节点数为:2^h - 1.
那么我们来对 root 节点的左右子树进行高度统计,分别记为 left 和 right,有以下两种结果:
- left == right。这说明,左子树一定是满二叉树,因为节点已经填充到右子树了,左子树必定已经填满了。所以左子树的节点总数我们可以直接得到,是 2^left - 1,加上当前这个 root 节点,则正好是 2^left。再对右子树进行递归统计。
- left != right。说明此时最后一层不满,但倒数第二层已经满了,可以直接得到右子树的节点个数。同理,右子树节点 +root 节点,总数为 2^right。再对左子树进行递归查找。
class Solution {
public int countNodes(TreeNode root) {
if(root==null) return 0;
int left = countLevel(root.left);
int right = countLevel(root.right);
if(left == right){
return countNodes(root.right) + (1<<left);
}else{
return countNodes(root.left) + (1<<right);
}
}
public static int countLevel(TreeNode root){
int level = 0;
while(root!=null){
level++;
root = root.left;
}
return level;
}
}
普通二叉树上寻找两个节点的最近公共祖先
测试链接:. - 力扣(LeetCode)
深度优先搜素
考虑通过递归对二叉树进行先序遍历,当遇到节点 p 或 q 时返回。从底至顶回溯,当节点 p,q 在节点 root的异侧时,节点 root即为最近公共祖先,则向上返回 root;
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root == null|| 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 null;
if(left==null) return right;
if(right==null) return left;
return root;
}
}
搜索二叉树上寻找两个节点的最近公共祖先
测试链接 : . - 力扣(LeetCode)
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
// root从上到下
// 如果先遇到了p,说明p是答案
// 如果先遇到了q,说明q是答案
// 如果root在p~q的值之间,不用管p和q谁大谁小,只要root在中间,那么此时的root就是答案
// 如果root在p~q的值的左侧,那么root往右移动
// 如果root在p~q的值的右侧,那么root往左移动
while (root != null) {
if (root.val < p.val && root.val < q.val)
root = root.right;
else if (root.val > p.val && root.val > q.val)
root = root.left;
else break;
}
return root;
}
}
收集累加和等于aim的所有路径
测试链接 : . - 力扣(LeetCode)
本题是典型的回溯问题,解法包含先序遍历 + 路径记录两部分:
设计到还原递归状态
class Solution {
LinkedList<List<Integer>> ans = new LinkedList<>();
LinkedList<Integer> path = new LinkedList<>();
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
recur(root, targetSum);
return ans;
}
public void recur(TreeNode root, int targetSum) {
//终止条件: 若节点 root 为空,则直接返回
if (root == null)
return;
// 将节点加入path
path.add(root.val);
// targetsum-root.val
targetSum -= root.val;
if (targetSum == 0 && root.left == null && root.right == null) {
// 找到了路径放入ans
ans.add(new LinkedList<Integer>(path));
}
recur(root.left, targetSum);
recur(root.right, targetSum);
//还原
path.removeLast();
}
}
验证平衡二叉树
测试链接 : . - 力扣(LeetCode)
class Solution {
public boolean isBalanced(TreeNode root) {
return height(root) != -1;
}
public int height(TreeNode root) {
if (root == null)
return 0;
int lh = height(root.left);
//当深度为-1时代表,左(右)子树不平衡 返回-1,会直接返回给上层 剪枝
if (lh == -1)
return -1;
int rh = height(root.right);
if (rh == -1)
return -1;
return Math.abs(lh - rh) < 2 ? Math.max(lh, rh) + 1 : -1;
}
}
验证搜索二叉树
测试链接 : . - 力扣(LeetCode)
递归
如果该二叉树的左子树不为空,则左子树上所有节点的值均小于它的根节点的值; 若它的右子树不空,则右子树上所有节点的值均大于它的根节点的值;它的左右子树也为二叉搜索树
class Solution {
public boolean isValidBST(TreeNode root) {
return isValidBST(root, Long.MIN_VALUE, Long.MAX_VALUE);
}
public boolean isValidBST(TreeNode root, Long lower, Long upper) {
if (root == null)
return true;
if (root.val <= lower || root.val >= upper)
return false;
return isValidBST(root.left, lower, Long.valueOf(root.val)) && isValidBST(root.right, Long.valueOf(root.val), upper);
}
}
全局变量法
class Solution {
//记录子树的最小和最大值
public static long min, max;
public boolean isValidBST(TreeNode head) {
// 当前节点 大于左子树的最大值 小于右子树的最小值
// 自底向上,可以记录下最大最小值
if (head == null) {
min = Long.MAX_VALUE;
max = Long.MIN_VALUE;
return true;
}
boolean lok = isValidBST(head.left);
long lmin = min;
long lmax = max;
boolean rok = isValidBST(head.right);
long rmin = min;
long rmax = max;
min = Math.min(Math.min(lmin, rmin), head.val);
max = Math.max(Math.max(lmax, rmax), head.val);
return lok && rok && lmax < head.val && head.val < rmin;
}
}
修剪搜索二叉树
测试链接 : . - 力扣(LeetCode)
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);
}
if (root.val > high) {
return trimBST(root.left, low, high);
}
root.left = trimBST(root.left, low, high);
root.right = trimBST(root.right, low, high);
return root;
}
}
二叉树打家劫舍问题
测试链接 : . - 力扣(LeetCode)
全局变量法(有一点dp的感觉)
class Solution {
// 全局变量,完成了X子树的遍历,返回之后
// yes变成,X子树在偷头节点的情况下,最大的收益
public static int yes;
// 全局变量,完成了X子树的遍历,返回之后
// no变成,X子树在不偷头节点的情况下,最大的收益
public static int no;
public int rob(TreeNode root) {
f(root);
return Math.max(yes, no);
}
public static void f(TreeNode root) {
if (root == null) {
yes = 0;
no = 0;
} else {
int y = root.val;
int n = 0;
f(root.left);
//此时全局变量记录的是左子树的最大收益
y += no;
n += Math.max(yes, no);
f(root.right);
y += no;
n += Math.max(yes, no);
yes = y;
no = n;
}
}
}