所有问题
| 序号 | 题目 | 完成 |
|---|
一、树的遍历
1.1 问题列表
| 序号 | 题目 | 完成 |
|---|---|---|
| 94. 二叉树的中序遍历 | √ | |
| 102. 二叉树的层序遍历 | √ | |
| 103. 二叉树的锯齿形层次遍历 | √ | |
| 1104. 二叉树寻路 | ||
| 637. 二叉树的层平均值 | √ | |
| 199. 二叉树的右视图 | √ | |
| 144. 二叉树的前序遍历 | √ | |
| 145. 二叉树的后序遍历 | √ | |
| 513. 找树左下角的值 | √ | |
| 100. 相同的树 | √ | |
| 剑指 Offer 26. 树的子结构 | √ |
1.2 题解
94.二叉树的中序遍历
递归写法很简单,要写出迭代写法才行。
// 递归写法
class Solution {
List<Integer> ans;
public List<Integer> inorderTraversal(TreeNode root) {
ans = new ArrayList<>();
dfs(root);
return ans;
}
public void dfs(TreeNode root) {
if (root == null) {
return;
}
dfs(root.left);
ans.add(root.val);
dfs(root.right);
}
}
// 迭代写法
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> ans = new ArrayList<>();
Deque<TreeNode> stack = new ArrayDeque<>();
while (!stack.isEmpty() || root != null) {
while (root != null) {
stack.push(root);
root = root.left;
}
root = stack.pop();
ans.add(root.val);
root = root.right;
}
return ans;
}
}
层序遍历的模板,一定要会背。
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> ans = new ArrayList<>();
if (root == null) {
return ans;
}
LinkedList<TreeNode> nodes = new LinkedList<TreeNode>();
nodes.add(root);
while (!nodes.isEmpty()) {
int size = nodes.size();
List<Integer> floor = new ArrayList<>();
for (int i = 0; i < size; i++) {
TreeNode cur = nodes.poll();
floor.add(cur.val);
if (cur.left != null) {
nodes.add(cur.left);
}
if (cur.right != null) {
nodes.add(cur.right);
}
}
ans.add(floor);
}
return ans;
}
}
// 递归写法
class Solution {
List<Integer> ans = new ArrayList<>();
public List<Integer> preorderTraversal(TreeNode root) {
dfs(root);
return ans;
}
public void dfs(TreeNode root) {
if (root == null) {
return;
}
ans.add(root.val);
dfs(root.left);
dfs(root.right);
}
}
// 迭代写法
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> ans = new ArrayList<>();
LinkedList<TreeNode> stack = new LinkedList<TreeNode>();
while (!stack.isEmpty() || root != null) {
while (root != null) {
ans.add(root.val);
stack.push(root);
root = root.left;
}
root = stack.pop();
root = root.right;
}
return ans;
}
}
// 递归写法
class Solution {
List<Integer> ans = new ArrayList<>();
public List<Integer> postorderTraversal(TreeNode root) {
dfs(root);
return ans;
}
public void dfs(TreeNode root) {
if (root == null) {
return;
}
dfs(root.left);
dfs(root.right);
ans.add(root.val);
}
}
// 迭代写法
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> ans = new ArrayList<>();
LinkedList<TreeNode> stack = new LinkedList<TreeNode>();
TreeNode pre = null;
while (!stack.isEmpty() || root != null) {
while (root != null) {
stack.push(root);
root = root.left;
}
root = stack.pop();
// 1. 最左边的节点,root.right ==null
// 2. 最左边节点的父节点
// 2.1 此时如果有right,应该先遍历right,即else分支做的,把root先放回去,root指向right
// 2.1.1 先把right打印出来,pre指向right
// 2.1.2 这个时候需要打印right的parent,此时pop出来的是root,root.right == pre就是为了在right也记录完之后记录root用
if (root.right == null || root.right == pre) {
ans.add(root.val);
pre = root;
root = null;
} else {
// 此时有右子树,将root重新放回去,从right开始
stack.push(root);
root = root.right;
}
}
return ans;
}
}
class Solution {
public List<Integer> rightSideView(TreeNode root) {
List<Integer> ans = new ArrayList<>();
if (root == null) {
return ans;
}
// 记录每一层最后一个节点就可以了
LinkedList<TreeNode> stack = new LinkedList<>();
stack.offer(root);
while (!stack.isEmpty()) {
int size = stack.size();
for (int i = 0; i < size; i++) {
TreeNode cur = stack.poll();
if (cur.left != null) {
stack.offer(cur.left);
}
if (cur.right != null) {
stack.offer(cur.right);
}
if (i == size - 1) {
ans.add(cur.val);
}
}
}
return ans;
}
}
class Solution {
public List<Double> averageOfLevels(TreeNode root) {
List<Double> ans = new ArrayList<>();
if (root == null) {
return ans;
}
LinkedList<TreeNode> stack = new LinkedList<>();
stack.offer(root);
while (!stack.isEmpty()) {
int size = stack.size();
double total = 0d;
for (int i = 0; i < size; i++) {
TreeNode cur = stack.poll();
// 将每一层的节点的值加起来
total += cur.val;
if (cur.left != null) {
stack.offer(cur.left);
}
if (cur.right != null) {
stack.offer(cur.right);
}
// 在这一层即将结束时,计算这一层的平均值
if (i == size - 1) {
ans.add(total / size);
}
}
}
return ans;
}
}
class Solution {
public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
List<List<Integer>> ans = new ArrayList<>();
if (root == null) {
return ans;
}
LinkedList<TreeNode> queue = new LinkedList<>();
queue.offer(root);
// 为true表示从右到左
// 为false表示从左到右
boolean reverse = false;
while (!queue.isEmpty()) {
int size = queue.size();
LinkedList<Integer> floor = new LinkedList<>();
for (int i = 0; i < size; i++) {
TreeNode cur = queue.poll();
if (reverse) {
// 从右到左,addFirst
floor.addFirst(cur.val);
} else {
floor.addLast(cur.val);
}
if (cur.left != null) {
queue.offer(cur.left);
}
if (cur.right != null) {
queue.offer(cur.right);
}
}
//翻转
reverse = !reverse;
ans.add(floor);
}
return ans;
}
}
class Solution {
public List<Integer> pathInZigZagTree(int label) {
int row = 1, rowStart = 1;
// 找到当前元素在哪一层
while (rowStart * 2 <= label) {
row++;
rowStart *= 2;
}
// 如果是偶数层,把label转换
if (row % 2 == 0) {
label = getReverse(label, row);
}
LinkedList<Integer> path = new LinkedList<Integer>();
// 开始遍历
while (row > 0) {
// 偶数层需要翻转,偶数层是从右到左遍历的
if (row % 2 == 0) {
path.addFirst(getReverse(label, row));
} else {
// 奇数层不用,因为奇数层是从左到右遍历的
path.addFirst(label);
}
row--;
label >>= 1;
}
return path;
}
public int getReverse(int label, int row) {
return (1 << row - 1) + (1 << row) - 1 - label;
}
}
class Solution {
public int findBottomLeftValue(TreeNode root) {
int ans = 0;
LinkedList<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
int size = queue.size();
for (int i = 0; i < size; i++) {
TreeNode cur = queue.poll();
// 每一层的第一个元素都记录,最后ans就是最后一层的第一个元素
if (i == 0) {
ans = cur.val;
}
if (cur.left != null) {
queue.offer(cur.left);
}
if (cur.right != null) {
queue.offer(cur.right);
}
}
}
return ans;
}
}
class Solution {
public boolean isSameTree(TreeNode p, TreeNode q) {
if (p == null && q == null) {
return true;
}
if (p == null || q == null) {
return false;
}
// 根节点相同并且左右子树都相同
return p.val == q.val && isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
}
}
class Solution {
// b是否为a的子结构
public boolean isSubStructure(TreeNode a, TreeNode b) {
// a走到最后了,也没有找到和b匹配的结构
// 如果给的条件b是null,那显然是false,这里隐藏了一个条件,a和b都为空的时候,不是子结构
if (a == null || b == null) {
return false;
}
// a在递归,等找到和b相同的节点后,就可以用hasSubStruct比较了
return hasSubStruct(a, b) || isSubStructure(a.left, b) || isSubStructure(a.right, b);
}
// 如果a,b根节点相同,那么b是不是a的子结构
public boolean hasSubStruct(TreeNode a, TreeNode b) {
// b遍历到null,说明b的所有节点都在a上遍历过了,此时b是a的子结构
if (b == null) {
return true;
}
// 如果a走到null,b没有走到null,显然不是子结构
// 如果a也没有走到null,此时比较a和b的值,如果值不等,显然也不是子结构
if (a == null || a.val != b.val) {
return false;
}
// 继续检查左子树和右子树
return hasSubStruct(a.left, b.left) && hasSubStruct(a.right, b.right);
}
}
二、树的构造
2.1 问题列表
| 序号 | 题目 | 完成 |
|---|---|---|
| 1 | 105. 从前序和中序遍历序列构造二叉树 | √ |
|3|106. 从中序和后序遍历序列构造二叉树|√| |4|889. 根据前序和后序遍历序列构造二叉树|√| |2|剑指 Offer 07. 重建二叉树|√|
105.从前序和中序遍历序列构造二叉树
相同问题:剑指 Offer 07. 重建二叉树
这个题目需要掌握递归和迭代两种写法。
// 递归
class Solution {
public TreeNode buildTree(int[] preorder, int[] inorder) {
return buildTree(preorder, 0, preorder.length - 1, inorder, 0, inorder.length - 1);
}
public TreeNode buildTree(int[] preorder, int start1, int end1, int[] inorder, int start2, int end2) {
if (start1 > end1 || start2 > end2) {
return null;
}
int rootVal = preorder[start1];
TreeNode root = new TreeNode(rootVal);
int leftSize = 0;
for (int i = start2; i <= end2; i++) {
if (inorder[i] == rootVal) {
break;
}
leftSize++;
}
root.left = buildTree(preorder, start1 + 1, end1, inorder, start2, start2 + leftSize - 1);
root.right = buildTree(preorder, start1 + leftSize + 1, end1, inorder, start2 + leftSize + 1, end2);
return root;
}
}
// 迭代的写法,需要背
class Solution {
public TreeNode buildTree(int[] preorder, int[] inorder) {
if (preorder == null || preorder.length == 0) {
return null;
}
TreeNode root = new TreeNode(preorder[0]);
Deque<TreeNode> stack = new LinkedList<TreeNode>();
stack.push(root);
int inorderIndex = 0;
for (int i = 1; i < preorder.length; i++) {
TreeNode node = stack.peek();
int preorderVal = preorder[i];
// 还没有走到最左边
if (node.val != inorder[inorderIndex]) {
node.left = new TreeNode(preorderVal);
stack.push(node.left);
} else {
// 栈顶的元素和当前index是相等的,说明走到了最左边,此时需要考虑right
while (!stack.isEmpty() && stack.peek().val == inorder[inorderIndex]) {
node = stack.pop();
inorderIndex++;
}
node.right = new TreeNode(preorderVal);
stack.push(node.right);
}
}
return root;
}
}
106.从中序和后序遍历序列构造二叉树
// 递归写法
class Solution {
Map<Integer, Integer> rootLocation = new HashMap<>();
public TreeNode buildTree(int[] inorder, int[] postorder) {
for (int i = 0; i < inorder.length; i++) {
rootLocation.put(inorder[i], i);
}
return build(inorder, 0, inorder.length - 1, postorder, 0, postorder.length - 1);
}
public TreeNode build(int[] inorder, int start1, int end1, int[] postorder, int start2, int end2) {
if (start1 > end1 || start2 > end2) {
return null;
}
int rootVal = postorder[end2];
int rootIn = rootLocation.get(rootVal);
int leftSize = rootIn - start1;
TreeNode root = new TreeNode(rootVal);
root.left = build(inorder, start1, rootIn - 1, postorder, start2, start2 + leftSize - 1);
root.right = build(inorder, rootIn + 1, end1, postorder, start2 + leftSize, end2 - 1);
return root;
}
}
889.根据前序和后序遍历序列构造二叉树
// 递归写法
class Solution {
Map<Integer, Integer> rootLocation = new HashMap<>();
public TreeNode constructFromPrePost(int[] preorder, int[] postorder) {
for (int i = 0; i < postorder.length; i++) {
rootLocation.put(postorder[i], i);
}
return buildTree(preorder, 0, preorder.length - 1, postorder, 0, postorder.length - 1);
}
public TreeNode buildTree(int[] preorder, int start1, int end1, int[] postorder, int start2, int end2) {
if (start1 > end1 || start2 > end2) {
return null;
}
// 只要一个元素的时候,直接返回
// 因为下面需要取start1后一个元素作为left,只有一个元素时,会报错
if (start1 == end1) {
return new TreeNode(preorder[start1]);
}
int rootVal = preorder[start1];
TreeNode root = new TreeNode(rootVal);
int left = preorder[start1 + 1];
int index = rootLocation.get(left);
int leftSize = index - start2 + 1;
root.left = buildTree(preorder, start1 + 1, start1 + leftSize, postorder, start2, index);
root.right = buildTree(preorder, start1 + 1 + leftSize, end1, postorder, index + 1, end2 - 1);
return root;
}
}
三、树的深度和宽度
其实深度或者直径也是依赖树的遍历。
3.1 问题列表
| 序号 | 题目 | 完成 |
|---|---|---|
| 104. 二叉树的最大深度 | ||
| 剑指 Offer 55 - I. 二叉树的深度 | ||
| 559. N 叉树的最大深度 | ||
| 111. 二叉树的最小深度 | ||
| 993. 二叉树的堂兄弟节点 | ||
| 543. 二叉树的直径 | ||
| 1522. N 叉树的直径 | ||
| 1245. 树的直径 | ||
| 2246. 相邻字符不同的最长路径 | ||
| 662. 二叉树最大宽度 |
3.2 题解
解题思路:遍历二叉树。
// 递归,利用左右子树的深度
class Solution {
public int maxDepth(TreeNode root) {
if (root == null) {
return 0;
}
if (root.left == null && root.right == null) {
return 1;
}
int maxDepth = Integer.MIN_VALUE;
// 这里加上left!=null的判断,是因为如果不加,那叶子节点的深度会被清零
if (root.left != null) {
maxDepth = Math.max(maxDepth(root.left), maxDepth);
}
if (root.right != null) {
maxDepth = Math.max(maxDepth(root.right), maxDepth);
}
return maxDepth + 1;
}
}
//时间复杂度:O(n),所有节点遍历一遍
//空间复杂度:O(height),递归需要栈空间,栈空间取决于递归的深度
// 同样是递归,但是利用了depth这个来做为层数
class Solution {
int depth;
int maxDepth;
public int maxDepth(TreeNode root) {
dfs(root);
return maxDepth;
}
public void dfs(TreeNode node) {
if (node == null) {
return;
}
depth++;
if (node.left == null && node.right == null) {
maxDepth = Math.max(maxDepth, depth);
}
dfs(node.left);
dfs(node.right);
depth--;
}
}
//时间复杂度:O(n),所有节点遍历一遍
//空间复杂度:O(height),递归需要栈空间,栈空间取决于递归的深度
和二叉树的区别就在于遍历子树的方法不同,其他都一样的。
class Solution {
public int maxDepth(Node root) {
if (root == null) {
return 0;
}
if (root.children == null || root.children.isEmpty()) {
return 1;
}
int maxDepth = Integer.MIN_VALUE;
// 从所有子树的最大深度中找到最大值
for (Node child : root.children) {
maxDepth = Math.max(maxDepth(child), maxDepth);
}
return maxDepth + 1;
}
}
//时间复杂度:O(n),所有节点遍历一遍
//空间复杂度:O(height),递归需要栈空间,栈空间取决于递归的深度
// 递归解法
class Solution {
public int minDepth(TreeNode root) {
if(root == null){
return 0;
}
if(root.left == null && root.right == null){
return 1;
}
int minDepth = Integer.MAX_VALUE;
// 得到左子树的最小深度
if(root.left!=null){
minDepth = Math.min(minDepth(root.left), minDepth);
}
// 得到右子树的最小深度
if(root.right!=null){
minDepth = Math.min(minDepth(root.right), minDepth);
}
// 最短的左子树和右子树深度加上自身节点
return minDepth+1;
}
}
//时间复杂度:O(n),所有节点遍历一遍
//空间复杂度:O(height),递归需要栈空间,栈空间取决于递归的深度
//迭代解法
class Solution {
public int minDepth(TreeNode root) {
if(root == null){
return 0;
}
LinkedList<TreeNode> queue = new LinkedList<>();
queue.add(root);
// 深度就是层数,用一个字段来记录层数
int floor = 1;
while(!queue.isEmpty()){
int size = queue.size();
for(int i=0;i<size;i++){
TreeNode cur = queue.pop();
// 找到的第一个叶子节点就是最小的深度
if(cur.left == null && cur.right == null){
return floor;
}
if(cur.left!=null){
queue.offer(cur.left);
}
if(cur.right!=null){
queue.offer(cur.right);
}
}
floor++;
}
return floor;
}
}
//时间复杂度:O(n),所有节点遍历一遍
//空间复杂度:O(n),队列需要保存所有节点
// DFS
class Solution {
private int max;
public int diameterOfBinaryTree(TreeNode root) {
// 注意直径是边数,不是节点数,直径=左子树最长的深度 + 右子树最长的深度
maxDepth(root);
return max;
}
public int maxDepth(TreeNode root) {
if (root == null) {
return 0;
}
// 左子节点的最大深度
int left = maxDepth(root.left);
// 右子节点的最大深度
int right = maxDepth(root.right);
max = Math.max(max, left + right);
// 返回当前节点的最大深度
return Math.max(left, right) + 1;
}
}
//leetcode submit region end(Prohibit modification and deletion)
// 时间复杂度:O(n),所有节点遍历一遍
// 空间复杂度:O(height),递归需要栈空间,栈空间取决于递归的深度
获取最大的两个子树深度可以用优先队列,也可以直接写:
int first = 0;
int second = 0;
for (Node child : root.children) {
int tmp = maxDepth(child);
if (tmp > first) {
// 把原来最大的置为第二大值
second = first;
// 更新最大值
first = tmp;
} else if (tmp > second) {
// 只更新第二大值
second = tmp;
}
}
class Solution {
int max;
public int diameter(Node root) {
// 所有子树中最深的两个子树的深度之和
maxDepth(root);
return max;
}
public int maxDepth(Node root) {
if (root == null) {
return 0;
}
if (root.children != null && !root.children.isEmpty()) {
// 用一个大顶堆来实现获取最大的两个子树深度
PriorityQueue<Integer> maxQueue = new PriorityQueue<>((o1, o2) -> {
return o2 - o1;
});
for (Node child : root.children) {
maxQueue.add(maxDepth(child));
}
int first = maxQueue.isEmpty() ? 0 : maxQueue.poll();
int second = maxQueue.isEmpty() ? 0 : maxQueue.poll();
// 计算最深的两条路径之和中的最大值
max = Math.max(first + second, max);
return first + 1;
}
return 1;
}
}
// 时间复杂度:O(n),所有节点遍历一遍
// 空间复杂度:O(height),递归需要栈空间,栈空间取决于递归的深度
class Solution {
int xParent;
int yParent;
int xDepth;
int yDepth;
boolean xFound = false;
boolean yFound = false;
public boolean isCousins(TreeNode root, int x, int y) {
dfs(root, 0, -1, x, y);
// 找到x和y的深度已经他们的父节点,最后判断是否为堂兄弟
return xDepth == yDepth && xParent != yParent;
}
public void dfs(TreeNode node, int depth, int parent, int x, int y) {
if (node == null) {
return;
}
// 更新深度和父节点
if (node.val == x) {
xFound = true;
xParent = parent;
xDepth = depth;
} else if (node.val == y) {
yFound = true;
yParent = parent;
yDepth = depth;
}
// 两个节点都找到了,就不用再遍历了
if (xFound && yFound) {
return;
}
// 没找到,继续left
dfs(node.left, depth + 1, node.val, x, y);
// 两个节点都找到了,就不用再遍历了
if (xFound && yFound) {
return;
}
// 没找到,继续right
dfs(node.right, depth + 1, node.val, x, y);
}
}
//BFS解法
class Solution {
public boolean isCousins(TreeNode root, int x, int y) {
LinkedList<TreeNode> queue = new LinkedList<>();
queue.offer(root);
// 层序遍历,堂兄弟必然在同一层
while (!queue.isEmpty()) {
int size = queue.size();
boolean matchX = false;
boolean matchY = false;
for (int i = 0; i < size; i++) {
TreeNode cur = queue.poll();
if (cur.val == x) {
matchX = true;
}
if (cur.val == y) {
matchY = true;
}
if (cur.left != null) {
queue.offer(cur.left);
}
if (cur.right != null) {
queue.offer(cur.right);
}
// 如果x和y为亲兄弟,直接退出
if (cur.left != null && cur.right != null) {
if ((cur.left.val == x || cur.left.val == y) && (cur.right.val == x || cur.right.val == y)) {
return false;
}
}
}
if (matchX && matchY) {
// 找到了堂兄弟,直接退出
return true;
} else if (matchX || matchY) {
// 找到一个节点,另一个没找到,此时不可能存在堂兄弟了,所以也直接退出
return false;
}
}
// 遍历了所有节点都没有找到堂兄弟节点,此时退出了
return false;
}
}
[To Review]1245. 树的直径
在做图章节的时候,需要和这个结合再加深理解。
多叉树其实就是无向图吧。
class Solution {
int max = Integer.MIN_VALUE;
public int treeDiameter(int[][] edges) {
if (edges.length == 0) {
return 0;
}
List<Integer>[] mappings = new List[edges.length + 1];
for (int i = 0; i < mappings.length; i++) {
mappings[i] = new ArrayList<>();
}
for (int[] edge : edges) {
int from = edge[0];
int to = edge[1];
mappings[from].add(to);
}
dfs(0, mappings);
return max;
}
// root节点的最大深度
public int dfs(Integer root, List<Integer>[] mappings) {
// 节点为空了,深度没有意义,返回0
if (root == null) {
return 0;
}
// 防止重复
visited[root] = true;
// 不存在子树,则深度为1
List<Integer> children = mappings[root];
if (children.size() == 0) {
return 1;
}
int first = 0;
int second = 0;
for (Integer child : children) {
int tmp = dfs(child, mappings);
if (tmp > first) {
second = first;
first = tmp;
} else if (tmp > second) {
second = tmp;
}
}
max = Math.max(first + second, max);
return first + 1;
}
}
// 时间复杂度:O(n)
// 空间复杂度:O(n)
其实也可以转换为N叉树的直径问题。
//leetcode submit region begin(Prohibit modification and deletion)
class Solution {
int max = 1;
String source;
public int longestPath(int[] parent, String s) {
int n = parent.length;
List<Integer>[] childrens = new List[n];
source = s;
for (int i = 0; i < n; i++) {
childrens[i] = new ArrayList<>();
}
for (int i = 1; i < n; i++) {
childrens[parent[i]].add(i);
}
dfs(0, childrens);
return max;
}
public int dfs(Integer root, List<Integer>[] childrens) {
// 节点为空了,深度没有意义,返回0
if (root == null) {
return 0;
}
// 不存在子树,则深度为1
List<Integer> children = childrens[root];
if (children.size() == 0) {
return 1;
}
int first = 0;
int second = 0;
for (Integer child : children) {
int tmp = dfs(child, childrens);
if (source.charAt(child) == source.charAt(root)) {
continue;
}
if (tmp > first) {
second = first;
first = tmp;
} else if (tmp > second) {
second = tmp;
}
}
max = Math.max(first + second + 1, max);
return first + 1;
}
}
// 时间复杂度:O(n)
// 空间复杂度:O(n)
四、树的转换
4.1 问题列表
| 序号 | 题目 | 完成 |
|---|---|---|
| 538. 把二叉树转换成累加树 | √ | |
| 226. 翻转二叉树 | √ | |
| 剑指 Offer 27. 二叉树的镜像 | √ | |
| 617. 合并二叉树 | √ | |
| 971. 翻转二叉树以匹配先序遍历 | ||
| 114. 二叉树展开为链表(先序遍历) | √ | |
| 109. 有序链表转换二叉搜索树 | ||
| 剑指 Offer 36. 二叉搜索树与双向链表 | ||
| 426. 将二叉搜索树转化为排序的双向链表 |
4.2 题解
- 把二叉树转换成累加树
class Solution {
int sum=0;
public TreeNode convertBST(TreeNode root) {
if(root == null){
return null;
}
// 遍历右子树
convertBST(root.right);
// 处理root的val
sum += root.val;
root.val = sum;
// 遍历左子树
convertBST(root.left);
return root;
}
}
class Solution {
// 递归函数的定义:将root的左右子树翻转
public TreeNode invertTree(TreeNode root) {
if (root == null) {
return null;
}
// 得到翻转后的左子树
TreeNode left = invertTree(root.left);
// 得到翻转后的右子树
TreeNode right = invertTree(root.right);
root.left = right;
root.right = left;
// 返回根节点
return root;
}
}
class Solution {
public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
// 都为null的时候返回null
if (root1 == null && root2 == null) {
return null;
}
// 重新new一个节点
TreeNode root = new TreeNode();
// 计算节点的值
root.val = (root1 == null ? 0 : root1.val) + (root2 == null ? 0 : root2.val);
// 获得左节点
TreeNode left = mergeTrees(root1 == null ? null : root1.left, root2 == null ? null : root2.left);
// 获得右节点
TreeNode right = mergeTrees(root1 == null ? null : root1.right, root2 == null ? null : root2.right);
root.left = left;
root.right = right;
return root;
}
}
class Solution {
// 定义:将以 root 为根的树拉平为链表
public void flatten(TreeNode root) {
if (root == null) {
return;
}
// 先把左右子树拉平
flatten(root.left);
flatten(root.right);
TreeNode left = root.left;
TreeNode right = root.right;
// 将root的左子树置空
root.left = null;
// 将root的右子树连接到left
root.right = left;
// 将右子树接上去,注意要找到left的最后一个节点来接
TreeNode p = root;
while (p.right != null) {
p = p.right;
}
p.right = right;
}
}
利用了中序遍历的顺序,并且用了一个last节点来记录上一次遍历结果。
class Solution {
Node first;
Node last;
public Node treeToDoublyList(Node root) {
if(root == null){
return null;
}
helper(root);
last.right = first;
first.left = last;
return first;
}
public void helper(Node root){
if(root == null){
return;
}
helper(root.left);
if(last!=null){
root.left = last;
last.right = root;
}else{
// 第一次遇到的节点,肯定是最小的节点
first = root;
}
last = root;
helper(root.right);
}
}
五、路径问题
5.1 问题列表
5.2 题解
5.2.1 最长的路径
- 求最深的那条路径
- 给一棵边权树树找到最大路径,要找到两个端点怎么办
5.2.2 路径总和
5.2.2.1 从根节点出发到某一节点
是否存在目标的路径和
class Solution {
int pathSum;
boolean rs;
public boolean hasPathSum(TreeNode root, int targetSum) {
dfs(root, targetSum);
return rs;
}
public void dfs(TreeNode root, int target) {
if (root == null) {
return;
}
pathSum += root.val;
// root是叶子节点,并且路径和等于目标
if (root.left == null && root.right == null && pathSum == target) {
rs = true;
return;
}
dfs(root.left, target);
dfs(root.right, target);
pathSum -= root.val;
}
}
// 时间复杂度:O(n),所有节点遍历一遍
// 空间复杂度:O(height),递归需要栈空间,栈空间取决于递归的深度
输出所有目标路径和的路径
class Solution {
List<List<Integer>> rs;
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
rs = new ArrayList<>();
dfs(root, targetSum, 0, new LinkedList<Integer>());
return rs;
}
public void dfs(TreeNode root, int target, int pathSum, LinkedList<Integer> paths) {
if (root == null) {
return;
}
// 将当前节点记入路径和
pathSum += root.val;
paths.add(root.val);
// root是叶子节点,并且路径和等于目标
if (root.left == null && root.right == null && pathSum == target) {
// 记录该路径
rs.add(new ArrayList<>(paths));
}
dfs(root.left, target, pathSum, paths);
dfs(root.right, target, pathSum, paths);
// 将当前节点从路径和里删除
pathSum -= root.val;
paths.removeLast();
}
}
5.2.2.1 从任意节点出发
输出所有目标路径和的路径条数
遍历方向始终是向下
class Solution {
// 递归函数的定义,root(包含自己)的所有路径中,路径和等于target的路径条数
// 退出条件,root==null,此时没有其他路径,所以应该返回0
public int pathSum(TreeNode root, int targetSum) {
if(root == null){
return 0;
}
int ret = rootSum(root, (long) targetSum);
ret += pathSum(root.left, targetSum);
ret += pathSum(root.right, targetSum);
return ret;
}
// 递归函数的定义,从root节点出发,遍历到的路径中,路径和等于target的路径条数
// 退出条件,root==null,此时表示从null出发,那肯定是0
public int rootSum(TreeNode root, Long targetSum) {
int ret = 0;
// 退出条件为root==null
if (root == null) {
return 0;
}
// 发现了目标路径和,结果+1,此时不能退出遍历,还需要继续遍历下去
if (root.val == targetSum) {
ret++;
}
ret += rootSum(root.left, targetSum - root.val);
ret += rootSum(root.right, targetSum - root.val);
return ret;
}
}
// 时间复杂度:O(n*n)
// 空间复杂度:O(height)
// 前缀和解法
class Solution {
// 前缀和出现的次数
Map<Long, Integer> map = new HashMap<>();
public int pathSum(TreeNode root, int targetSum) {
map.put(0L, 1);
return dfs(root, targetSum, 0L);
}
//在root为根节点的子树中,存在节点之间路径和为target的路径总数
public int dfs(TreeNode root, int target, long preSum) {
if (root == null) {
return 0;
}
int res = 0;
// 计算root到当前的前缀和
preSum += root.val;
res += getMatchedNodeCount(preSum, target);
recordPreSum(preSum);
// 再加上左右子树下的满足的路径和数
res += dfs(root.left, target, preSum);
res += dfs(root.right, target, preSum);
removePreSum(preSum);
return res;
}
// 如果存在node1到node2的路径和为target,那应该满足preSum2- preSum1 = target
// 所以想要知道满足preSum1的路径和的数量,应该取key=preSum2 - target
public int getMatchedNodeCount(long curSum, int target) {
return map.getOrDefault(curSum - target, 0);
}
public void recordPreSum(long preSum) {
map.put(preSum, map.getOrDefault(preSum, 0) + 1);
}
public void removePreSum(long preSum) {
map.put(preSum, map.get(preSum) - 1);
}
}
// 时间复杂度:O(n)
// 空间复杂度:O(n)
5.2.2.3 结点之和最大的路径
class Solution {
int max = Integer.MIN_VALUE;
// root下的所有路径中的最大路径和
public int maxPathSum(TreeNode root) {
findMax(root);
return max;
}
// 一个节点的最大贡献值
public int findMax(TreeNode root) {
if (root == null) {
return 0;
}
// 左子树的最大贡献值
int left = Math.max(findMax(root.left), 0);
// 右子树的最大贡献值
int right = Math.max(findMax(root.right), 0);
// 在遍历树的时候寻找每一个节点的最大路径和
max = Math.max(max, left + right + root.val);
// 左右子树可能为全负,此时只需要节点本身就够了
return root.val + Math.max(left, right);
}
}
//时间复杂度:O(n)
//空间复杂度:O(height)
5.2.2.4 从根节点到某一节点的路径
class Solution {
List<String> paths;
public int sumNumbers(TreeNode root) {
paths = new ArrayList<>();
traverse(root, new StringBuilder());
int total = 0;
for (String path : paths) {
total += Integer.parseInt(path);
}
return total;
}
public void traverse(TreeNode root, StringBuilder path) {
if (root == null) {
return;
}
path.append(root.val);
if (root.left == null && root.right == null) {
paths.add(path.toString());
}
traverse(root.left, path);
traverse(root.right, path);
path.deleteCharAt(path.length()-1);
}
}
//leetcode submit region end(Prohibit modification and deletion)
利用前缀和求解:
- 计算出每个节点上的数字
- 然后累加到root节点(正好用递归)
// 前缀和解法
class Solution {
public int sumNumbers(TreeNode root) {
return dfs(root, 0);
}
public int dfs(TreeNode root, int preSum){
if(root == null){
return 0;
}
// 计算当前节点的值
int sum = preSum * 10 +root.val;
if(root.left == null && root.right ==null){
return sum;
}else{
// 左右节点加起来最后累加到根节点
return dfs(root.left, sum) + dfs(root.right, sum);
}
}
}
// BFS遍历
import java.util.LinkedList;
class Solution {
public int sumNumbers(TreeNode root) {
LinkedList<TreeNode> nodes = new LinkedList<>();
// 用一个队列记录当前节点的值
LinkedList<Integer> nums = new LinkedList<>();
nodes.add(root);
nums.add(root.val);
int total = 0;
while (!nodes.isEmpty()) {
int size = nodes.size();
for (int i = 0; i < size; i++) {
TreeNode cur = nodes.poll();
int val = nums.pop();
// 到叶子节点时,计算路径总和
if (cur.left == null && cur.right == null) {
total += val;
}
if (cur.left != null) {
nodes.add(cur.left);
nums.add(val * 10 + cur.left.val);
}
if (cur.right != null) {
nodes.add(cur.right);
nums.add(val * 10 + cur.right.val);
}
}
}
return total;
}
}
// 时间复杂度:O(n),每个节点遍历一次
// 空间复杂度:O(n),每个节点记录一次
5.2.2.5 其他
其实计算从根到所有叶子的路径总和并不难,这一题的难点在于,题目给的是一串数字,要将这些数字转换为节点。
这里需要一些完全二叉树的知识。
import java.util.HashMap;
//leetcode submit region begin(Prohibit modification and deletion)
class Solution {
// 节点位置和原数字的映射关系
Map<Integer, Integer> mapping = new HashMap<>();
int total = 0;
public int pathSum(int[] nums) {
for (int num : nums) {
mapping.put(getNode(num), num);
}
// 从第0个数字开始遍历
dfs(nums[0], 0);
return total;
}
public void dfs(Integer root, int sum) {
// 节点为空,退出
if (root == null) {
return;
}
int val = getValue(root);
sum += val;
Integer left = getLeft(root);
Integer right = getRight(root);
// 一个叶子节点就是一条路径
if (left == null && right == null) {
// 计算到总和里
total += sum;
}
dfs(left, sum);
dfs(right, sum);
sum -= val;
}
public int getNode(int num) {
// 百分位作为层数
int floor = num / 100;
// 十分位作为位置
int index = (num - floor * 100) / 10;
// 个位作为值
int value = num - floor * 100 - index * 10;
// 计算得到当前节点的位置
return (int) Math.pow(2, floor - 1) - 1 + index;
}
public int getValue(int num) {
// 百分位作为层数
int floor = num / 100;
// 十分位作为位置
int index = (num - floor * 100) / 10;
// 个位作为值
return num - floor * 100 - index * 10;
}
public boolean isNull(int num) {
int pos = getNode(num);
// 不存在,我们可以认为节点为空
return !mapping.containsKey(pos);
}
public Integer getLeft(int num) {
int pos = getNode(num);
return mapping.get(pos * 2);
}
public Integer getRight(int num) {
int pos = getNode(num);
return mapping.get(pos * 2 + 1);
}
}
//leetcode submit region end(Prohibit modification and deletion)
- 二叉树输出给定节点到目标节点的路径
六、祖先问题
6.1 问题列表
| 序号 | 题目 | 完成 |
|---|---|---|
| 1 | [236]二叉树的最近公共祖先 | √ |
| 2 | [剑指 Offer 68 - II]二叉树的最近公共祖先 | √ |
| 3 | [1644]二叉树的最近公共祖先 II | √ |
| 4 | [1650]二叉树的最近公共祖先 III | √ |
| 5 | [1676]二叉树的最近公共祖先 IV | √ |
| 6 | [235]二叉搜索树的最近公共祖先 | √ |
| 7 | [剑指 Offer 68 - I]二叉搜索树的最近公共祖先 | √ |
| 8 | [1483]树节点的第 K 个祖先 | √ |
| 9 | 993. 二叉树的堂兄弟节点 | ✅ |
6.2 题解
两种解法:
- 用DFS遍历,定义f(x)为,x的自身或者子节点中包含p或q
- 先记录每一个节点的父节点,然后对其中一个往上找,找p和q的父节点,有没有重合的,第一个重合的父节点就是最近的公共祖先
// 解法一
class Solution {
TreeNode ans = null;
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
dfs(root, p, q);
return ans;
}
// root的子节点或自身包含p或q
public boolean dfs(TreeNode root, TreeNode p, TreeNode q) {
if (root == null) {
return false;
}
boolean left = dfs(root.left, p, q);
boolean right = dfs(root.right, p, q);
// 1. 同时包含p或q的时候,这是最理想的状态
// 2. 当前节点就是p或者q,此时left和right不可能同时为true
if ((left && right) || ((root.val == p.val || root.val == q.val) && (right || left))) {
ans = root;
}
// 左子树有、右子树有、当前节点就是
return left || right || (root.val == p.val || root.val == q.val);
}
}
// 解法二
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
Map<Integer, TreeNode> parent = new HashMap<>();
dfs(root, parent);
// 记录p的所有父节点
Set<Integer> visited = new HashSet<>();
while (p != null) {
visited.add(p.val);
p = parent.get(p.val);
}
// 再去遍历q的父节点,q的父节点肯定在visited中出现
while (q != null) {
if (visited.contains(q.val)) {
return q;
}
q = parent.get(q.val);
}
// 肯定可以找到最近公共祖先,最后返回null,只是方法需要一个返回值
return null;
}
public void dfs(TreeNode root, Map<Integer, TreeNode> parent) {
if (root.left != null) {
parent.put(root.left.val, root);
dfs(root.left, parent);
}
if (root.right != null) {
parent.put(root.right.val, root);
dfs(root.right, parent);
}
}
}
最容易想到的是从根节点往上遍历,就像236里的写法。不过这种写法是会超时的。
class TreeAncestor {
int[] parents;
public TreeAncestor(int n, int[] parent) {
parents = parent;
}
public int getKthAncestor(int node, int k) {
if(k == 0){
return node;
}
if(k <0){
return -1;
}
// 拿到当前节点的父节点
int parent = parents[node];
if(parent == -1){
return -1;
}
return getKthAncestor(parent, k-1);
}
}
[1644]二叉树的最近公共祖先 II
解法一:记录parents节点,然后先将p的parents节点都记录下来,再去遍历q的每一个parent节点,当可以与p的parent节点相遇,那么就找到了最近公共祖先。 解法二:利用fx,fx定义为root的子树下或自身存在p或q的节点。
//leetcode submit region begin(Prohibit modification and deletion)
import java.util.HashSet;
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
TreeNode ans = null;
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
dfs(root, p, q, false);
return ans;
}
public boolean dfs(TreeNode root, TreeNode p, TreeNode q, boolean exist) {
if (root == null) {
return false;
}
boolean left = dfs(root.left, p, q, exist);
boolean right = dfs(root.right, p, q, exist);
if ((left && right) || ((root.val == p.val || root.val == q.val) && (left || right))) {
if (!exist) {
// 当前节点就是LCA
ans = root;
exist = true;
}
}
return left || right || (root.val == p.val || root.val == q.val);
}
}
//leetcode submit region end(Prohibit modification and deletion)
[1650]二叉树的最近公共祖先 III
将p的parents节点都记录下来,再去遍历q的每一个parent节点,当可以与p的parent节点相遇,那么就找到了最近公共祖先。
解法1
class Solution {
public Node lowestCommonAncestor(Node p, Node q) {
Set<Integer> visited = new HashSet<>();
while (p != null) {
visited.add(p.val);
p = p.parent;
}
while (q != null) {
if (visited.contains(q.val)) {
return q;
}
q = q.parent;
}
return null;
}
}
// 解法2
class Solution {
public Node lowestCommonAncestor(Node p, Node q) {
// 可以认为是求相交链表
Node pp = p;
Node qq = q;
while (pp != qq) {
if (pp == null) {
pp = q;
} else {
pp = pp.parent;
}
if (qq == null) {
qq = p;
} else {
qq = qq.parent;
}
}
return pp;
}
}
[1676]二叉树的最近公共祖先 IV
// 暴力解法
class Solution {
Map<Integer, TreeNode> parents = new HashMap<>();
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode[] nodes) {
dfs(root);
Map<Integer, Integer> visited = new HashMap<>();
TreeNode p = nodes[0];
int start = 0;
while (p != null) {
visited.put(p.val, start++);
p = parents.get(p.val);
}
TreeNode ans = nodes[0];
int max = Integer.MIN_VALUE;
for (int i=1; i<nodes.length; i++) {
TreeNode q = nodes[i];
while (q != null) {
if (visited.containsKey(q.val)) {
int cur = visited.get(q.val);
if (cur > max) {
ans = q;
max = cur;
}
break;
}
q = parents.get(q.val);
}
}
return ans;
}
public void dfs(TreeNode root) {
if (root.left != null) {
parents.put(root.left.val, root);
dfs(root.left);
}
if (root.right != null) {
parents.put(root.right.val, root);
dfs(root.right);
}
}
}
//leetcode submit region end(Prohibit modification and deletion)
// 递归解法
class Solution {
// 递归定义:返回root的子树上nodes中的节点的最近公共祖先
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode[] nodes) {
if (root == null) {
return null;
}
// 如果nodes节点里包含root节点,显然最近的公共祖先就是root
// 因为在这一次的遍历里,root是根节点
for (TreeNode node : nodes) {
if (root == node) {
return root;
}
}
// 获取左右子树各自属于nodes中的节点的最近公共祖先
TreeNode left = lowestCommonAncestor(root.left, nodes);
TreeNode right = lowestCommonAncestor(root.right, nodes);
// 若左右子树均存在nodes中的节点,则最近公共祖先就是root
// 因为nodes里的节点分散在root的左右子树上了,单独的左子树或右子树无法完全覆盖所有的nodes节点
if (left != null && right != null) {
return root;
}
// 1. left和right都为null,在本题中是不可能的,因为肯定存在公共祖先,不过也不影响,当都为null时返回null即可
// 2. left和right有一个为null,不为null的子树上的公共祖先,即为所有属于nodes中节点的公共祖先
return left != null ? left : right;
}
}
[235]二叉搜索树的最近公共祖先
相同问题:[剑指 Offer 68 - I]二叉搜索树的最近公共祖先
二叉搜索树的特点是:所有左子树的值都小于根节点,所有右子树的值都大于根节点。
分析:
- 如果p在左,q在右,此时可能为root
- 如果p、q都在左,那肯定不在root,至少是root的左子树
- 如果p、q都在右,那也肯定不在root,至少是root的右子树
// 找到p<=root<=q的节点
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
TreeNode ancestor = root;
while (true) {
if (p.val < ancestor.val && q.val < ancestor.val) {
ancestor = ancestor.left;
} else if (p.val > ancestor.val && q.val > ancestor.val) {
ancestor = ancestor.right;
} else {
break;
}
}
return ancestor;
}
}
class Solution {
// x 的信息
int x;
TreeNode xParent;
int xDepth;
boolean xFound = false;
// y 的信息
int y;
TreeNode yParent;
int yDepth;
boolean yFound = false;
public boolean isCousins(TreeNode root, int x, int y) {
this.x = x;
this.y = y;
dfs(root, 0, null);
return xDepth == yDepth && xParent != yParent;
}
public void dfs(TreeNode node, int depth, TreeNode parent) {
if (node == null) {
return;
}
if (node.val == x) {
xParent = parent;
xDepth = depth;
xFound = true;
} else if (node.val == y) {
yParent = parent;
yDepth = depth;
yFound = true;
}
// 如果两个节点都找到了,就可以提前退出遍历
// 即使不提前退出,对最坏情况下的时间复杂度也不会有影响
if (xFound && yFound) {
return;
}
dfs(node.left, depth + 1, node);
if (xFound && yFound) {
return;
}
dfs(node.right, depth + 1, node);
}
}
七、树的序列化和反序列化
7.1 问题列表
| 序号 | 问题描述 | 完成 |
|---|---|---|
| 297. 二叉树的序列化与反序列化 | ✅ | |
| 428. 序列化和反序列化 N 叉树 | ✅ | |
| 449. 序列化和反序列化二叉搜索树 | ✅ |
public class Codec {
private String SEP = ",";
private String NULL = "#";
// Encodes a tree to a single string.
public String serialize(TreeNode root) {
StringBuilder sp = new StringBuilder();
dfs(root, sp);
return sp.deleteCharAt(sp.length() - 1).toString();
}
public void dfs(TreeNode root, StringBuilder sb) {
if (root == null) {
sb.append(NULL).append(SEP);
return;
}
sb.append(root.val).append(SEP);
dfs(root.left, sb);
dfs(root.right, sb);
}
// Decodes your encoded data to tree.
public TreeNode deserialize(String data) {
String[] datas = data.split(SEP);
LinkedList<String> vals = new LinkedList<>();
for (String d : datas) {
vals.add(d);
}
return construct(vals);
}
public TreeNode construct(LinkedList<String> vals) {
if (vals.isEmpty()) {
return null;
}
String first = vals.removeFirst();
if (first.equals(NULL)) {
return null;
}
TreeNode root = new TreeNode(Integer.parseInt(first));
root.left = construct(vals);
root.right = construct(vals);
return root;
}
}
- 时间复杂度:O(n),每一个节点都需要访问一次
- 空间复杂度:O(n),用了一个临时链表
用一般二叉树的序列化和反序列化也是可以AC的。还有优化的空间吗?
public class Codec {
public String serialize(TreeNode root) {
List<Integer> list = new ArrayList<Integer>();
postOrder(root, list);
String str = list.toString();
return str.substring(1, str.length() - 1);
}
public TreeNode deserialize(String data) {
if (data.isEmpty()) {
return null;
}
String[] arr = data.split(", ");
Deque<Integer> stack = new ArrayDeque<Integer>();
int length = arr.length;
for (int i = 0; i < length; i++) {
stack.push(Integer.parseInt(arr[i]));
}
return construct(Integer.MIN_VALUE, Integer.MAX_VALUE, stack);
}
private void postOrder(TreeNode root, List<Integer> list) {
if (root == null) {
return;
}
postOrder(root.left, list);
postOrder(root.right, list);
// 后序遍历
list.add(root.val);
}
private TreeNode construct(int lower, int upper, Deque<Integer> stack) {
if (stack.isEmpty() || stack.peek() < lower || stack.peek() > upper) {
return null;
}
int val = stack.pop();
TreeNode root = new TreeNode(val);
root.right = construct(val, upper, stack);
root.left = construct(lower, val, stack);
return root;
}
}
解题思路:
1、序列化:用[]将children围起来;
2、反序列化:用栈来实现层级关系。
class Codec {
private String SEP = ",";
private String START_TAG = "[";
private String END_TAG = "]";
// Encodes a tree to a single string.
public String serialize(Node root) {
if (root == null) {
return null;
}
StringBuilder sb = new StringBuilder();
dfs(root, sb);
return sb.deleteCharAt(sb.length() - 1).toString();
}
public void dfs(Node root, StringBuilder sb) {
if (root == null) {
return;
}
sb.append(root.val).append(SEP);
// 处理孩子节点
if (root.children != null) {
// 给孩子节点用括号围起来
sb.append(START_TAG).append(SEP);
for (Node child : root.children) {
dfs(child, sb);
}
sb.append(END_TAG).append(SEP);
}
}
// Decodes your encoded data to tree.
public Node deserialize(String data) {
if (data == null) {
return null;
}
String[] elements = data.split(SEP);
Stack<Node> stack = new Stack<Node>();
Node root = null;
Node cur = null;
for (String ele : elements) {
if (ele.equals(START_TAG)) {
stack.push(cur);
} else if (ele.equals(END_TAG)) {
stack.pop();
} else {
Node node = new Node(Integer.parseInt(ele));
node.children = new ArrayList<>();
if (root == null) {
root = node;
} else {
Node parent = stack.peek();
parent.children.add(node);
}
cur = node;
}
}
return root;
}
}
八、其他
8.1 平衡树
| 序号 | 问题描述 | 完成 |
|---|---|---|
| 110. 平衡二叉树 | ✅ |
其实还是遍历。
class Solution {
public boolean isBalanced(TreeNode root) {
if (root == null) {
return true;
}
int left = getHeight(root.left);
int right = getHeight(root.right);
// 1、左子树和右子树的高度差不超过1
// 2、左右子树自身也是平衡二叉树
return Math.abs(left - right) <= 1 && isBalanced(root.left) && isBalanced(root.right);
}
public int getHeight(TreeNode root) {
if (root == null) {
return 0;
}
return Math.max(getHeight(root.left), getHeight(root.right)) + 1;
}
}
8.2 二叉搜索树
二叉搜索树是一个很重要的数据结构,加上自平衡特性就形成了红黑树等各种数据结构,需要重点掌握。
移步:二叉搜索树
8.3 字典树&前缀树
| 序号 | 问题描述 | 完成 |
|---|---|---|
| 14. 最长公共前缀 | ||
| 139. 单词拆分 | ||
| 140. 单词拆分 II | ||
| 208. 实现 Trie (前缀树) |
8.4 完全二叉树
| 序号 | 问题描述 | 完成 |
|---|---|---|
| 222. 完全二叉树的节点个数 | ✅ |
class Solution {
public int countNodes(TreeNode root) {
TreeNode l = root, r = root;
int hl = 0, hr = 0;
// 计算左右子树的高度
while (l != null) {
l = l.left;
hl++;
}
while (r != null) {
r = r.right;
hr++;
}
// 如果高度相等,那么说明是完全二叉树
if (hl == hr) {
return (int) Math.pow(2, hl) - 1;
}
// 如果高度不等,还是要手动计算
return 1 + countNodes(root.left) + countNodes(root.right);
}
}
8.5 线段树
| 序号 | 问题描述 | 完成 |
|---|---|---|
| 53. 最大子数组和 | ✅ |
class Solution {
public class Status {
public int lSum, rSum, mSum, iSum;
public Status(int lSum, int rSum, int mSum, int iSum) {
this.lSum = lSum;
this.rSum = rSum;
this.mSum = mSum;
this.iSum = iSum;
}
}
public int maxSubArray(int[] nums) {
return getInfo(nums, 0, nums.length - 1).mSum;
}
public Status getInfo(int[] a, int l, int r) {
// 元素自身,每一个属性都是自己
if (l == r) {
return new Status(a[l], a[l], a[l], a[l]);
}
// 分治
int m = (l + r) >> 1;
Status lSub = getInfo(a, l, m);
Status rSub = getInfo(a, m + 1, r);
return pushUp(lSub, rSub);
}
public Status pushUp(Status l, Status r) {
// 区间和
int iSum = l.iSum + r.iSum;
// 以l开头的最大区间和
int lSum = Math.max(l.lSum, l.iSum + r.lSum);
// 以r结尾的最大区间和
int rSum = Math.max(r.rSum, r.iSum + l.rSum);
// 最大区间和
int mSum = Math.max(Math.max(l.mSum, r.mSum), l.rSum + r.lSum);
return new Status(lSum, rSum, mSum, iSum);
}
}