目录
注意二叉搜索树的性质。利用好会简单很多。
654. 最大二叉树
654与106极其类似,甚至更简单。
注意类似用数组构造二叉树的题目,每次分隔尽量不要定义新的数组,而是通过下标索引直接在原数组上操作,这样可以节约时间和空间上的开销。
class Solution {
public TreeNode constructMaximumBinaryTree(int[] nums) {
if (nums.length == 0) return null;
return constructMaximumBinaryTree1(nums, 0, nums.length);
}
private TreeNode constructMaximumBinaryTree1(int[] nums, int numsStart, int numsEnd) {
if (numsStart == numsEnd) return null;
int max = nums[numsStart];
int rootIndex = numsStart;
for (int i = numsStart; i < numsEnd; i++) {
if (nums[i] > max){
max = nums[i];
rootIndex = i;
}
}
TreeNode root = new TreeNode(max);
//左闭右开
root.left = constructMaximumBinaryTree1(nums, numsStart, rootIndex);
root.right = constructMaximumBinaryTree1(nums, rootIndex + 1, numsEnd);
return root;
}
}
617. 合并二叉树
本题使用递归超级简单。
递归法
对于每个节点,我们检查两棵树中该节点是否存在:
- 如果两个节点都存在,则将它们的值相加,并递归地合并它们的左子树和右子树。
- 如果某一棵树的节点不存在,则直接返回另一棵树的节点。
- 如果两个节点都不存在,则返回
null
。
class Solution {
public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
// 如果其中一棵树为空,返回另一棵树
if (root1 == null) return root2;
if (root2 == null) return root1;
// 如果两个节点都存在,则将它们的值相加
TreeNode mergedRoot = new TreeNode(root1.val + root2.val);
// 递归合并左子树和右子树
mergedRoot.left = mergeTrees(root1.left, root2.left);
mergedRoot.right = mergeTrees(root1.right, root2.right);
return mergedRoot;
}
}
直接修改root1,不用重新定义一个树:
前序(中左右)最好理解。其实中序(左中右)后序(左右中)都可以,直接换个位置即可。
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;
}
}
迭代法_层序遍历
层序遍历来合并两棵二叉树的思路:通过队列来同时遍历两棵树的节点,将它们的值相加。如果其中某一棵树的某个位置没有节点,直接使用另一棵树的节点。整个过程类似于 BFS(广度优先搜索)。
时间复杂度:
- 每个节点最多会被访问一次,时间复杂度为 O(n),其中 n 是两棵树节点的总数。
空间复杂度:
- 空间复杂度取决于队列中的最大节点数。最坏情况下,队列可能会同时存储树的一层节点数,因此空间复杂度为 O(n)。
使用队列迭代
class Solution {
public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
//层序遍历法
// 如果其中一棵树为空,返回另一棵树
if (root1 == null) return root2;
if (root2 == null) return root1;
// 创建一个队列来同时遍历两棵树
Queue<TreeNode[]> queue = new LinkedList<>();
// 将两棵树的根节点放入队列
queue.offer(new TreeNode[]{root1, root2});
// 遍历队列,直到所有节点处理完
while (!queue.isEmpty()) {
// 取出两个节点
TreeNode[] current = queue.poll();
TreeNode node1 = current[0];
TreeNode node2 = current[1];
// 将两棵树的节点值相加
node1.val += node2.val;
// 处理左子树
if (node1.left!=null && node2.left != null){
// 两棵树的左子树都存在,放入队列
queue.offer(new TreeNode[]{node1.left, node2.left});
} else if (node1.left == null) {
// 如果node1的左子树为空,直接用node2的左子树
node1.left = node2.left;
}
// 处理右子树
if (node1.right!=null && node2.right != null){
queue.offer(new TreeNode[]{node1.right , node2.right});
} else if (node1.right == null) {
node1.right = node2.right;
}
}
return root1;
}
}
使用栈迭代
class Solution {
// 使用栈迭代
public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
if (root1 == null) {
return root2;
}
if (root2 == null) {
return root1;
}
Stack<TreeNode> stack = new Stack<>();
stack.push(root2);
stack.push(root1);
while (!stack.isEmpty()) {
TreeNode node1 = stack.pop();
TreeNode node2 = stack.pop();
node1.val += node2.val;
if (node2.right != null && node1.right != null) {
stack.push(node2.right);
stack.push(node1.right);
} else {
if (node1.right == null) {
node1.right = node2.right;
}
}
if (node2.left != null && node1.left != null) {
stack.push(node2.left);
stack.push(node1.left);
} else {
if (node1.left == null) {
node1.left = node2.left;
}
}
}
return root1;
}
}
700. 二叉搜索树中的搜索
二叉搜索树是一个有序树:
- 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 它的左、右子树也分别为二叉搜索树
本题居然迭代法要简单很多!
递归法
利用好二叉搜索树的性质。
如果root->val > val,搜索左子树,如果root->val < val,就搜索右子树,最后如果都没有搜索到,就返回NULL。
class Solution {
public TreeNode searchBST(TreeNode root, int val) {
// // 递归法 普通二叉树
// if (root == null || root.val == val) return root;
// // 递归查找左子树
// TreeNode leftResult = searchBST(root.left, val);
// if (leftResult != null) {
// return leftResult; // 如果在左子树找到了结果,则返回
// }
// // 如果左子树没有找到,再递归查找右子树
// return searchBST(root.right, val);
// 递归法 二叉搜索树
if (root == null || root.val == val) return root;
if (val < root.val) return searchBST(root.left, val);
else return searchBST(root.right, val);
}
}
迭代法(简单)
因为二叉搜索树的特殊性,也就是节点的有序性,可以不使用辅助栈或者队列就可以写出迭代法。
对于一般二叉树,递归过程中还有回溯的过程,例如走一个左方向的分支走到头了,那么要调头,在走右分支。
而对于二叉搜索树,不需要回溯的过程,因为节点的有序性就帮我们确定了搜索的方向。
例如要搜索元素为3的节点,我们不需要搜索其他节点,也不需要做回溯,查找的路径已经规划好了。
中间节点如果大于3就向左走,如果小于3就向右走。
class Solution {
// 迭代,利用二叉搜索树特点,优化,可以不需要栈
public TreeNode searchBST(TreeNode root, int val) {
while (root != null)
if (val < root.val) root = root.left;
else if (val > root.val) root = root.right;
else return root;
return null;
}
}
class Solution {
// 迭代,普通二叉树
public TreeNode searchBST(TreeNode root, int val) {
if (root == null || root.val == val) {
return root;
}
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()) {
TreeNode pop = stack.pop();
if (pop.val == val) {
return pop;
}
if (pop.right != null) {
stack.push(pop.right);
}
if (pop.left != null) {
stack.push(pop.left);
}
}
return null;
}
}
98. 验证二叉搜索树
思路:
要知道中序遍历下,输出的二叉搜索树节点的数值是有序序列。
有了这个特性,验证二叉搜索树,就相当于变成了判断一个序列是不是递增的了。
这个题有2个陷阱:
- 陷阱1:(需要尤其注意,我就是掉进了这个坑)
不能单纯的比较左节点小于中间节点,右节点大于中间节点就完事了。
写出了类似这样的代码:
if (root->val > root->left->val && root->val < root->right->val) {
return true;
} else {
return false;
}
我们要比较的是 左子树所有节点小于中间节点,右子树所有节点大于中间节点。所以以上代码的判断逻辑是错误的。
例如:节点10大于左节点5,小于右节点15,但右子树里出现了一个6 这就不符合了!
- 陷阱2:
样例中最小节点 可能是int的最小值,如果这样使用最小的int来比较也是不行的。
此时可以初始化比较元素为longlong的最小值。
递归法
递归的思路:
- 中序遍历是按照“左 -> 中 -> 右”的顺序进行的。
- 当遍历左子树时,期望左子树中的所有节点都比当前节点小;然后处理当前节点,期望它比左子树的最大节点大;接着遍历右子树,期望右子树中的所有节点都比当前节点大。
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; // 更新 max 为当前节点
// 右 // 右子树递归
boolean right = isValidBST(root.right);
return right; // 返回右子树的验证结果
}
}
递归法简洁实现
// 简洁实现·递归解法
class Solution {
public boolean isValidBST(TreeNode root) {
return validBST(Long.MIN_VALUE, Long.MAX_VALUE, root);
}
boolean validBST(long lower, long upper, TreeNode root) {
if (root == null) return true;
if (root.val <= lower || root.val >= upper) return false;
return validBST(lower, root.val, root.left) && validBST(root.val, upper, root.right);
}
}
中序遍历简洁实现
// 简洁实现·中序遍历
class Solution {
private long prev = Long.MIN_VALUE;
public boolean isValidBST(TreeNode root) {
if (root == null) {
return true;
}
if (!isValidBST(root.left)) {
return false;
}
if (root.val <= prev) { // 不满足二叉搜索树条件
return false;
}
prev = root.val;
return isValidBST(root.right);
}
}
迭代法
1.使用统一迭代法
思路:
- 使用中序遍历来遍历二叉树的节点,确保遍历过程中当前节点的值始终大于之前访问过的节点的值。
- 采用栈(stack)的方式来模拟递归的中序遍历,避免使用系统递归调用。
- 每次从栈中取出一个节点时,如果这个节点不是
null
,说明我们还没有处理它,而是要先处理它的左子树。因此,将它重新压栈并插入一个null
作为标记,之后先处理左子树。 - 当我们遇到
null
时,表示左子树已经处理完毕,接下来我们可以处理当前节点了。
class Solution {
public boolean isValidBST(TreeNode root) {
//使用統一迭代法
Stack<TreeNode> stack = new Stack<>();
TreeNode pre = null; // 记录前一个访问的节点,用来和当前节点进行比较
if (root != null) stack.add(root);
while (!stack.isEmpty()) {
TreeNode curr = stack.peek(); // 查看栈顶的节点,不弹出
if (curr != null) { // 如果栈顶的节点不是 null,说明是正常的节点
stack.pop(); // 弹出栈顶的节点(即将要处理当前节点)
if (curr.right != null) // 右
stack.add(curr.right); // 中序遍历:右子树稍后处理
stack.add(curr); // 将当前节点重新压入栈,稍后处理(需要在遍历完左子树后处理)
stack.add(null); // 在当前节点之后压入一个 null 标记,表示这个节点稍后需要处理
if (curr.left != null) // 左
stack.add(curr.left);
} else { // 如果栈顶是 null,说明当前节点的左子树已经遍历完毕,可以处理当前节点了
stack.pop(); // 弹出 null 标记
TreeNode temp = stack.pop();
if (pre != null && pre.val >= temp.val)
return false;
pre = temp;
}
}
return true;
}
}
迭代法2:(中序遍历)
class Solution {
public boolean isValidBST(TreeNode root) {
// 迭代法
if (root == null) return true;
Stack<TreeNode> stack = new Stack<>();
TreeNode pre = null;
while(root != null || !stack.isEmpty()) {
// 先遍历左子树,将左边所有节点压栈
while(root != null) {
stack.push(root);
root = root.left; // 左
}
// 处理栈顶节点 中
TreeNode pop = stack.pop();
// 判断当前节点值是否大于前一个访问的节点
if (pre != null && pop.val <= pre.val)
return false;
pre = pop;
// 遍历右子树
root = pop.right; // 右
}
return true;
}
}
第十七天的总算是结束了,直冲Day18!