都明白的,带※的,肯定得多刷几遍才能明白~
100、相同的树(简单)
class Solution {
public boolean isSameTree(TreeNode p, TreeNode q) {
if (p == null && q == null) return true;
if (p == null || q == null || p.val != q.val) return false;
return isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
}
}
107、二叉树的层序遍历Ⅱ(中等)
在正常层序遍历的基础上,把后面的结果插在前面即可。
class Solution {
public List<List<Integer>> levelOrderBottom(TreeNode root) {
List<List<Integer>> ans = new LinkedList<>();
if (root == null) return ans;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
int sz = queue.size();
List<Integer> tmp = new LinkedList<>();
for (int i = 0; i < sz; i++) {
TreeNode t = queue.poll();
tmp.add(t.val);
if (t.left != null) queue.offer(t.left);
if (t.right != null) queue.offer(t.right);
}
ans.add(0, tmp);
}
return ans;
}
}
※199、二叉树的右视图(中等)
层序遍历,取最后一个元素即可。
class Solution {
public List<Integer> rightSideView(TreeNode root) {
List<Integer> ans = new LinkedList<>();
Queue<TreeNode> queue = new LinkedList<>();
// 特判一下
if (root == null) return ans;
queue.offer(root);
while (!queue.isEmpty()) {
int sz = queue.size();
for (int i = 0; i < sz; i++) {
TreeNode tmp = queue.poll();
if (i + 1 == sz) {
// 最后一个节点才add
ans.add(tmp.val);
}
if (tmp.left != null) queue.offer(tmp.left);
if (tmp.right != null) queue.offer(tmp.right);
}
}
return ans;
}
}
※※※111、二叉树的最小深度(简单)
还是想了一会,主要是左右节点为空的特判,需要注意一下。反正这种问题从宏观上去想,再加上base case。
class Solution {
public int minDepth(TreeNode root) {
if (root == null) return 0;
// 到达叶子节点,算一个
if (root.left == null && root.right == null) {
return 1;
}
// 左节点为空,只能去看右节点,当然也要算上自己
if (root.left == null) return 1 + minDepth(root.right);
// 右节点为空,只能去看左节点
if (root.right == null) return 1 + minDepth(root.left);
int leftMin = minDepth(root.left);
int rightMin = minDepth(root.right);
// 根节点自己+1
return Math.min(leftMin, rightMin) + 1;
}
}
※129、求根节点到叶节点数字之和(中等)
做了半天还没做出来,是自己太菜了…
class Solution {
public int sumNumbers(TreeNode root) {
return traverse(root, 0);
}
public int traverse(TreeNode root, int preSum) {
if (root == null) return 0;
// 顺便记录当前路径和
int sum = preSum * 10 + root.val;
if (root.left == null && root.right == null) {
return sum;
}
// 求的是到达叶子节点的所有路径值的总和,所以还需要去考虑左右子树的情况
return traverse(root.left, sum) + traverse(root.right, sum);
}
}
其实和上一题差不多,都需要先判断叶子节点,从而提前结束判断,如果不是叶子节点就还需要继续
当然用迭代也是可以做的:(递归就是用队列,你有几个参数用几个队列存储(一般而言))
class Solution {
public int sumNumbers(TreeNode root) {
Queue<TreeNode> nodes = new LinkedList<>();
Queue<Integer> sum = new LinkedList<>();
nodes.offer(root);
sum.offer(0);
int ans = 0;
while (!nodes.isEmpty()) {
int size = nodes.size();
for (int i = 0; i < size; i++) {
TreeNode cur = nodes.poll();
int tmp = sum.poll();
tmp = tmp * 10 + cur.val;
if (cur.left == null && cur.right == null) {
ans += tmp;
}
if (cur.left != null) {
nodes.offer(cur.left);
sum.offer(tmp);
}
if (cur.right != null) {
nodes.offer(cur.right);
sum.offer(tmp);
}
}
}
return ans;
}
}
贴一道和上面思想差不多的题目:
257、二叉树的所有路径(简单)
所有路径就不说了,DFS专项呀!(BFS也能做),注意StringBuffer的引用问题!!!
class Solution {
List<String> ans = new LinkedList<>();
public List<String> binaryTreePaths(TreeNode root) {
traverse(root, new StringBuffer());
return ans;
}
void traverse(TreeNode root, StringBuffer sb) {
if (root == null) return;
// 满足叶子节点条件
if (root.left == null && root.right == null) {
sb.append(root.val);
ans.add(sb.toString());
return;
}
sb.append(root.val).append("->");
traverse(root.left, new StringBuffer(sb));
traverse(root.right, new StringBuffer(sb));
}
}
再来试试迭代求解(害怕HR问呀…)
class Solution {
public List<String> binaryTreePaths(TreeNode root) {
List<String> ans = new LinkedList<>();
if (root == null) return ans;
// 两个队列来模拟
Queue<TreeNode> Node = new LinkedList<>();
Node.offer(root);
Queue<StringBuffer> Sb = new LinkedList<>();
Sb.offer(new StringBuffer());
while (!Node.isEmpty()) {
int size = Node.size();
for (int i = 0; i < size; i++) {
TreeNode cur = Node.poll();
TreeNode left = cur.left;
TreeNode right = cur.right;
StringBuffer sb = Sb.poll();
if (left == null && right == null) {
StringBuffer tmp = new StringBuffer(sb);
tmp.append(cur.val);
ans.add(tmp.toString());
continue;
}
StringBuffer tmp = new StringBuffer(sb);
tmp.append(cur.val).append("->");
if (left != null) {
Node.offer(left);
Sb.offer(tmp);
}
if (right != null) {
Node.offer(right);
Sb.offer(tmp);
}
}
}
return ans;
}
}
※103、二叉树的锯齿形层序遍历(中等)
这道题还是考察层序遍历,只不过要对遍历的高度进行记录,主要是区别偶数和奇数的两种不同方案。
class Solution {
public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
List<List<Integer>> ans = new LinkedList<>();
Queue<TreeNode> queue = new LinkedList<>();
if (root == null) return ans;
queue.offer(root);
int cnt = 1;
while (!queue.isEmpty()) {
int sz = queue.size();
List<Integer> tmp = new LinkedList<>();
for (int i = 0; i < sz; i++) {
TreeNode t = queue.poll();
if (cnt % 2 == 0) {
// 偶数层,从右往左
tmp.add(0, t.val);
} else {
// 奇数层,从左往右
tmp.add(t.val);
}
if (t.left != null) queue.offer(t.left);
if (t.right != null) queue.offer(t.right);
}
// 层数++
cnt++;
ans.add(tmp);
}
return ans;
}
}
上面两道题都是面试常考题目,从本质上来说真的很简单,只要会层序遍历就秒了。
※222、完全二叉树的节点个数(中等)
可以给出一种平凡解,适用于所有二叉树:用层序遍历累加每层size即可。
class Solution {
public int countNodes(TreeNode root) {
if (root == null) return 0;
// 完全二叉树的特性,最后一层可能不全满,但不为空
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
int ans = 0;
while (!queue.isEmpty()) {
int sz = queue.size();
ans += sz;
for (int i = 0; i < sz; i++) {
TreeNode cur = queue.poll();
if (cur.left != null) queue.offer(cur.left);
if (cur.right != null) queue.offer(cur.right);
}
}
return ans;
}
}
这样并没有利用上完全二叉树的性质,该怎么办?完全二叉树,一定有部分是满二叉树,另一部分可以是也可以不是,对于满二叉树,我们可以直接返回其结果,而对于非满二叉树,就继续递归往下求解。
class Solution {
public int countNodes(TreeNode root) {
if (root == null) return 0;
TreeNode leftNode = root;
TreeNode rightNode = root;
// 记录左右子树高度
int leftH = 0;
int rightH = 0;
while (leftNode != null) {
leftNode = leftNode.left;
leftH++;
}
while (rightNode != null) {
rightNode = rightNode.right;
rightH++;
}
// 完全二叉树肯定有一部分是满二叉树,另一部分可以是也可以不是
if (leftH == rightH) {
// 满二叉树
return (int)Math.pow(2, leftH) - 1;
}
// 如果不是满二叉树,那就看左右子树,再统计
// 反正总有一部分可以用满二叉树公式求出
return 1 + countNodes(root.left) + countNodes(root.right);
}
}
※404、左叶子之和(简单)
找到当前节点的左子节点,确定它是叶子节点,求和。
class Solution {
public int sumOfLeftLeaves(TreeNode root) {
if (root == null) return 0;
int sum = 0;
// 找到当前节点的左子节点,并保证它是叶子节点即可
if (root.left != null && root.left.left == null && root.left.right == null) {
sum = root.left.val;
}
return sum + sumOfLeftLeaves(root.left) + sumOfLeftLeaves(root.right);
}
}
117、填充每个节点的下一个右侧节点指针 II(中等)
本题和填充每个节点的下一个右侧节点指针区别在于本题不再是完全二叉树,需要考虑的因素较多,用递归就不太适合了,更适合使用层序遍历进行。
下面是116题目的代码,比较简单,就是拆分成两个子树进行连接
class Solution {
public Node connect(Node root) {
if (root == null) return root;
// 初始状态下,所有的next指针都设置为NULL,所以根节点不用管
setNext(root.left, root.right);
return root;
}
void setNext(Node root1, Node root2) {
if (root1 == null) {
// 完美二叉树,没有左子节点一定没有右子节点
// 没有右子节点,一定没有左子节点
return;
}
// 左右连接起来
root1.next = root2;
setNext(root1.left, root1.right);
setNext(root1.right, root2.left);
setNext(root2.left, root2.right);
}
}
上面的代码没有用队列,所以空间复杂度为O(1),下面给出使用队列的层序遍历解法(空间复杂度为O(n)):
class Solution {
public Node connect(Node root) {
if (root == null) return root;
Queue<Node> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
int sz = queue.size();
for (int i = 0; i < sz; i++) {
Node cur = queue.poll();
// 拿出来的节点一定会和当前队列中的头结点相连
// 因为我们是按照左、右子结点入队的
if (i < sz - 1) {
// 最后一个结点就不能再连接了
cur.next = queue.peek();
}
if (cur.left != null) {
queue.offer(cur.left);
}
if (cur.right != null) {
queue.offer(cur.right);
}
}
}
return root;
}
}
完全二叉树简化了做题难度,回到117,普通的二叉树该怎么办?
用层序遍历,和上面一摸一样的代码…
class Solution {
public Node connect(Node root) {
if (root == null) return root;
Queue<Node> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
int sz = queue.size();
for (int i = 0; i < sz; i++) {
Node cur = queue.poll();
if (i < sz - 1) {
// 这一层最后一个点就不用连了
cur.next = queue.peek();
}
if (cur.left != null) queue.offer(cur.left);
if (cur.right != null) queue.offer(cur.right);
}
}
return root;
}
}
有空继续刷! 咱们继续!
二叉树满足某种条件问题
※110、平衡二叉树(简单)
class Solution {
public boolean isBalanced(TreeNode root) {
if (root == null) return true;
int left = traverse(root.left);
int right = traverse(root.right);
if (Math.abs(left - right) > 1) return false;
return isBalanced(root.left) && isBalanced(root.right);
}
// 求当前节点的最大高度(也就是取决于当前节点的左右子节点)
int traverse(TreeNode root) {
if (root == null) return 0;
// 求左右子树高度
int left = traverse(root.left);
int right = traverse(root.right);
// 求最大值,还要包括根节点
return Math.max(left, right) + 1;
}
}
上面的计算属于自顶向下,造成了很多次重复计算(递归套递归,绝对是一种很蠢的写法),之前算过的节点的最大高度,之后无法利用上,还需要重新计算,可能会想用备忘录存储,问题是,本题没有说每个节点的值都是唯一的,无法存储。
为了避免上面的问题,我们可以使用自底向上的递归。还是要求左右子树的最大高度,只不过多了判断的步骤,平衡二叉树的每一个节点的左右子树的高度差的绝对值都不能超过1,那我们从底部开始往上搜,一旦有不满足情况的子树,那以后的根节点,遍历到同样这棵子树时,肯定也不满足,直接返回。
class Solution {
public boolean isBalanced(TreeNode root) {
return traverse(root) >= 0;
}
int traverse(TreeNode root) {
if (root == null) return 0;
int leftHeight = traverse(root.left);
int rightHeight = traverse(root.right);
// 写在后序位置,这样就能够知道左右子树情况
if (leftHeight == -1 || rightHeight == -1 || Math.abs(leftHeight - rightHeight) > 1) {
// 用-1代表不满足要求
return -1;
}
// 正常求左右子树最大高度
return Math.max(leftHeight, rightHeight) + 1;
}
}
自底向上的递归就是及时止损 自顶向下的递归就是不管三七二十一先把所有的高度算一遍
,一定要学会自底向上的递归。
下面给出一道跟上面解法差不多的题目:
剑指OfferⅡ47、二叉树剪枝(中等)
和上面一题一样,也是要找节点是否满足什么条件,那我们就从最最最最底层的情况开始分析,那就是只有一个节点,如果它的值为0,且为叶子节点,那就直接删除,return null,这个判断应该写在后序位置,因为这样才能知道它的左右节点的全部信息。
class Solution {
public TreeNode pruneTree(TreeNode root) {
// 要删除所有为0的子树
if (root == null) return null;
root.left = pruneTree(root.left);
root.right = pruneTree(root.right);
// 判断一定要写在后序位置,这样才能知道左右子树的情况
if (root.val == 0 && root.left == null && root.right == null) {
return null;
}
return root;
}
}
和上面代码对比,不就只有if判断语句不同,以及root节点的赋值不同~
※※617、合并二叉树(简单)
这道题做了半天也没对,还是自己想复杂了,我们要知道当前节点的左右节点,那就只能在后序位置生成新的二叉树。
class Solution {
public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
// 左边没有只能看看右边
if (root1 == null) return root2;
// 右边没有只能看看左边
if (root2 == null) return root1;
TreeNode merged = new TreeNode(root1.val + root2.val);
// 后序位置一定知道当前根节点的左右节点
merged.left = mergeTrees(root1.left, root2.left);
merged.right = mergeTrees(root1.right, root2.right);
return merged;
}
}
※※606、根据二叉树构建字符串(简单)
我们可以站在后序位置来看当前节点的左右子节点(拿到左右子节点的结果,合并在一起就完事了)
要注意,右子树为空时,需要省略掉不必要的括号
class Solution {
public String tree2str(TreeNode root) {
if (root == null) return "";
// 站在后序位置,左右子树的情况都已经知道
// 对于叶子节点,直接返回自己的值,忽略掉不必要的括号
if (root.left == null && root.right == null) {
// 叶子节点直接返回自己的值
return Integer.toString(root.val);
}
String left = tree2str(root.left);
String right = tree2str(root.right);
StringBuilder ans = new StringBuilder();
// 如果右子树为空,则不需要添加新的括号
// 只有左子树为空时,才需要添加新的括号
if (root.right == null) {
return ans.append(root.val).append("(").append(left).append(")").toString();
}
// 左右子树都不为空
return ans.append(root.val).append("(").append(left).append(")(").append(right).append(")").toString();
}
}
二叉树的展开问题
※※114、二叉树展开为链表(中等)
class Solution {
public void flatten(TreeNode root) {
if (root == null) return;
flatten(root.left);
flatten(root.right);
// 左右都拉直了
TreeNode left = root.left;
TreeNode right = root.right;
root.right = root.left;
// 找到右边尾节点
TreeNode p = root;
// 左边被拿掉了
p.left = null;
while (p.right != null) {
p = p.right;
}
// 把右边拉直的练到尾部右边
p.right = right;
}
}
※※897、递增顺序搜索树(简单)
这道题和上面一道题是一样的,你可能不清楚递归函数内部到底干了什么,但是你知道它有这个作用,那就直接拿来用。
class Solution {
public TreeNode increasingBST(TreeNode root) {
if (root == null) return null;
// 假设目前root的左右子树都已经拉平了
root.right = increasingBST(root.right);
root.left = increasingBST(root.left);
if (root.left != null) {
// 根据题目意思必须从小到大建,那就必须建在左子树上
TreeNode tmp = root.left;
// root左子树清零(与left断开连接,但left还是存在在那里)
root.left = null;
// 保存tmp的初始节点
TreeNode p = tmp;
// 找到tmp的最右边,也就是最大位置,放root
while (tmp.right != null) {
tmp = tmp.right;
}
// root身上还带着右子树的,所以直接等于就可以了
tmp.right = root;
return p;
} else {
return root;
}
}
}
上面一题因为根节点就是最小节点,所以直接把左子树放到根节点右边,再找到最右边尾部节点,放右子树,根节点是最小的,所以放完之后返回根节点即可。但是本题,根节点不是最小的,最小的在根节点的左子树(BST的特性),所以一切建树行为都要发生在左子树上,那就先找到左子树最后位置放根节点就行,根节点的右子树一直跟着根节点的,不用管。正是因为需要建立在左子树上,所以才需要考虑左子树是否为空!
这里有个关于Java语言的特性问题需要说明一下,就是引用,看下面两个代码:
上面这个代码,node b = a.left,我把a.left置为null,但是b并不会等于null。
上面这里的a.left,是由新的空间开辟的,a.left只是指向那个node(3,4),所以a.left = null实际是让a与node(3,4)断开连接(不指向它,但它实际是存在的),b则一直指向node(3,4),也是没有断开连接。
再看下面这个代码:
我修改了a.left.x的值,node b = a.left,为什么此时b的x值也会跟着修改?,因为b是node(3,4)的引用,a.left也是node(3,4)的引用,它们指向同一个node(3,4)并且现在只有一个node(3,4),它们都可以修改node(3,4),一旦有一个动了node(3,4),其余指向node(3,4)的内容都会改变。
我们首先得看看引用变量:
比如int a = 1; a就是变量的名字,1就是变量的值。
而当变量指向一个对象时,这个变量就被称为引用变量
比如A a =new A();
a就是引用变量,它指向了一个A对象,也可以说它引用了一个A对象。我们通过操纵这个a来操作A对象。 此时,变量a的值为它所引用对象的地址。每当new的时候就会开辟一个空间,而我写A b = a,并不会开辟新的空间,而是让b指向a,这就是引用。
二叉树的最近公共祖先问题
※※236、二叉树的最近公共祖先(中等)
对于当前根节点,可能pq都在其左子树,pq都在其右子树,也可能pq分居左右子树,也有可能pq都不存在,考虑这几种情况,相信这个递归函数去做。
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root == null || root == p || root == q) return root;
// 我们相信这个递归函数,它能够找到左右子树中关于p、q的最近公共祖先
TreeNode left = lowestCommonAncestor(root.left, p, q);
TreeNode right = lowestCommonAncestor(root.right, p, q);
// 到达后序位置,说明前面的left、right我们已经知道了,现在就来check
// pq全在右子树,那就直接返回right
if (left == null) {
return right;
}
// pq全在左子树,直接返回left
if (right == null) {
return left;
}
// pq分局左右子树,返回root
if (left != null && right != null) return root;
// 没找到pq,null
return null;
}
}
再来一道升级版的题目:
※235、二叉搜索树的最近公共祖先(简单)
BST是特殊的二叉树,所以上一题代码也适用,关键是如何利用BST的特性。
二叉搜索树性质决定了:如果 p.val 和 q.val 都比 root.val 小,则p、q肯定在 root 的左子树。
摘自:https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-search-tree/solution/di-gui-he-die-dai-fa-by-hyj8/
也就是说如果p q分居异侧,直接返回root,如果root == p或者root == q,也可以直接返回root
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (p.val > root.val && q.val > root.val) {
// 一定在右子树
return lowestCommonAncestor(root.right, p, q);
}
if (p.val < root.val && q.val < root.val) {
// 一定在左子树
return lowestCommonAncestor(root.left, p, q);
}
// 在异侧,或在root自身身上
return root;
}
}
N叉树的遍历问题
※589、N叉树的前序遍历(简单)
和二叉树相比,无非是子节点多了很多,不再只有左右子节点,但是使用递归添加节点值的位置还是不变的,依旧是在前序位置add。
class Solution {
List<Integer> ans = new LinkedList<>();
public List<Integer> preorder(Node root) {
traverse(root);
return ans;
}
void traverse(Node root) {
if (root == null) return;
// 还是前序位置add
ans.add(root.val);
// 遍历所有孩子(不分左右了)
for (Node t : root.children) {
traverse(t);
}
}
}
我们都知道,递归其实是通过栈(先进后出)实现的,那我们可以模拟栈的实现过程,从来实现迭代方法:
java中用Deque双端队列,来模拟栈Stack
class Solution {
public List<Integer> preorder(Node root) {
List<Integer> ans = new LinkedList<>();
Deque<Node> stack = new ArrayDeque<>();
if (root == null) return ans;
stack.push(root);
while (!stack.isEmpty()) {
// 模拟栈
Node tmp = stack.pop();
ans.add(tmp.val);
List<Node> list = tmp.children;
int size = list.size();
// 保证出栈顺序按照:根->左->右,即前序遍历顺序
// 倒着入栈,就能够正着出栈
for (int i = size - 1; i >= 0; i--) {
Node t = list.get(i);
if (t == null) continue;
stack.push(t);
}
}
return ans;
}
}
※590、N叉树的后序遍历(简单)
同样直接遍历所有孩子,并且把add放在后序位置。
class Solution {
List<Integer> ans = new LinkedList<>();
public List<Integer> postorder(Node root) {
traverse(root);
return ans;
}
void traverse(Node root) {
if (root == null) return;
for (Node tmp : root.children) {
traverse(tmp);
}
// 后序位置
ans.add(root.val);
}
}
同样给出迭代写法,相当于是前序遍历的逆序,这里将每个节点值都插到ans头部位置,由于后序遍历顺序:左右根,所以在存节点时,要把右节点放在最前面,这样每次栈顶取出的元素插到ans头部位置,才能满足左右根的顺序。
class Solution {
public List<Integer> postorder(Node root) {
List<Integer> ans = new LinkedList<>();
Deque<Node> stack = new ArrayDeque<>();
if (root == null) return ans;
stack.push(root);
while (!stack.isEmpty()) {
Node tmp = stack.pop();
List<Node> t = tmp.children;
int size = t.size();
// 每次把结果加到头部
ans.add(0, tmp.val);
for (int i = 0; i < size; i++) {
if (t.get(i) == null) continue;
stack.push(t.get(i));
}
}
return ans;
}
}
429、N叉树的层序遍历(中等)
class Solution {
public List<List<Integer>> levelOrder(Node root) {
List<List<Integer>> ans = new LinkedList<>();
Queue<Node> queue = new LinkedList<>();
if (root == null) return ans;
queue.offer(root);
while (!queue.isEmpty()) {
int sz = queue.size();
List<Integer> tmp = new LinkedList<>();
for (int i = 0; i < sz; i++) {
Node cur = queue.poll();
tmp.add(cur.val);
// 遍历所有孩子
for (Node next : cur.children) {
if (next == null) continue;
queue.offer(next);
}
}
ans.add(tmp);
}
return ans;
}
}
有同学会问,为什么N叉树没有中序遍历?确实,N叉树的中序遍历是很难确定的,普通的二叉树,可以左根右,但是N叉树呢?由于它有多个孩子,也就不存在中序遍历这个概念了(因为没有左右子树呀)。而前、后序遍历,只区别根节点放在第一个位置,还是最后一个位置,中间对孩子的遍历都是从左到右,不论有多少孩子都能够实现遍历。
559、N叉树的最大深度(简单)
class Solution {
public int maxDepth(Node root) {
if (root == null) return 0;
// 初始化为0,深度不可能为负
int max = 0;
for (Node t : root.children) {
max = Math.max(max, maxDepth(t));
}
// 二叉树写法
// int leftMax = maxDepth(root.left);
// int rightMax = maxDepth(root.right);
// 后序位置,前面的所有孩子深度都知道了,且求出了最大的,加上根节点自己即可
return max + 1;
}
}
同样给出迭代解法,利用层序遍历来找。
class Solution {
public int maxDepth(Node root) {
if (root == null) return 0;
Queue<Node> queue = new LinkedList<>();
queue.offer(root);
int depth = 0;
while (!queue.isEmpty()) {
int size = queue.size();
for (int i = 0; i < size; i++) {
Node t = queue.poll();
for (Node tN : t.children) {
queue.offer(tN);
}
}
// 当前这一层玩完了,深度++
depth++;
}
return depth;
}
}
※1361、验证二叉树(中等)
刚开始还在一直用矩阵记录出度入度,然后一堆判断,还是有几个案例过不了,例如有两个连通分量,一个连通分量成环,一个成二叉树,显然是错的,所以这道题应该用并查集。(因为它可以帮助连接节点,并且判断当前连接是否有环存在)
连用并查集判断是否只存在一棵树,再根据一棵二叉树的性质去判断(每个节点的入度小于等于1 && 入度总和等于节点数 - 1)
class Solution {
public boolean validateBinaryTreeNodes(int n, int[] leftChild, int[] rightChild) {
UF uf = new UF(n);
for (int i = 0; i < n; i++) {
if (leftChild[i] != -1) {
uf.union(i, leftChild[i]);
}
if (rightChild[i] != -1) {
uf.union(i, rightChild[i]);
}
}
// 连通分量 = 1
if (uf.count() != 1) return false;
// 统计每个节点的入度 <= 1,且节点的入度和 = 节点数 - 1
int[] inDegree = new int[n];
for (int i = 0; i < n; i++) {
int left = leftChild[i];
int right = rightChild[i];
if (left != -1 && ++inDegree[left] > 1) return false;
if (right != -1 && ++inDegree[right] > 1) return false;
}
int total = 0;
for (int degree : inDegree) {
total += degree;
}
return total == n - 1;
}
}
// 并查集模板
class UF {
int count;
int[] parent;
int[] size;
UF(int n) {
this.count = n;
this.parent = new int[n];
this.size = new int[n];
for (int i = 0; i < n; i++) {
parent[i] = i;
size[i] = 1;
}
}
int find(int x) {
// 路径压缩
while (x != parent[x]) {
parent[x] = parent[parent[x]];
x = parent[x];
}
return x;
}
void union(int p, int q) {
int rootP = find(p);
int rootQ = find(q);
if (rootP == rootQ) return;
// 小树接在大树后
if (size[rootP] > size[rootQ]) {
parent[rootQ] = rootP;
size[rootP] += size[rootQ];
} else {
parent[rootP] = rootQ;
size[rootQ] += size[rootP];
}
// 连通分量--
count--;
}
boolean connected(int p, int q) {
return find(p) == find(q);
}
int count() {
return count;
}
}