文章目录
654. 最大二叉树
思路
构造树一般采用的是前序遍历,因为先构造中间节点,然后递归构造左子树和右子树。
// 在左闭右开区间[left, right),构造二叉树
注意类似用数组构造二叉树的题目,每次分隔尽量不要定义新的数组,而是通过下标索引直接在原数组上操作,这样可以节约时间和空间上的开销。
代码:
递归三部曲:
确定递归函数的参数和返回值
参数传入的是存放元素的数组,返回该数组构造的二叉树的头结点,返回类型是指向节点的指针。终结点判断:数组是否为空
确定单层递归的逻辑
这里有三步工作
- 先要找到数组中最大的值和对应的下标, 最大的值构造根节点,下标用来下一步分割数组。
- 最大值所在的下标左区间 构造左子树
这里要判断maxValueIndex > 0,因为要保证左区间至少有一个数值。 - 最大值所在的下标右区间 构造右子树
判断maxValueIndex < (nums.size() - 1),确保右区间至少有一个数值
class Solution {
public TreeNode constructMaximumBinaryTree(int[] nums) {
return travesal(nums,0,nums.length);
}
public TreeNode travesal(int[] nums,int begin,int end){
// if(nums.length==1){
// return new TreeNode(nums[0]);
// }
if(end-begin<=0){
// 没有元素了
return null;
}
int maxVal=-1;
int maxIndex=0;
// for(int i=0;i<nums.length;i++){//边界条件不对
for(int i=begin;i<end;i++){
if(nums[i]>maxVal){
maxVal=nums[i];
maxIndex=i;
}
}
TreeNode node = new TreeNode(maxVal);
node.left=travesal(nums,begin,maxIndex);
node.right=travesal(nums,maxIndex+1,end);
return node;
}
}
617. 合并二叉树
思路:递归
二叉树使用递归,就要想使用前中后哪种遍历方式?
本题使用哪种遍历都是可以的!
以前序遍历为例:
那么我们来按照递归三部曲来解决:
确定递归函数的参数和返回值:
首先要合入两个二叉树,那么参数至少是要传入两个二叉树的根节点,返回值就是合并之后二叉树的根节点。确定终止条件:
因为是传入了两个树,那么就有两个树遍历的节点t1 和 t2,如果t1 == NULL 了,两个树合并就应该是 t2 了(如果t2也为NULL也无所谓,合并之后就是NULL)。反过来如果t2 == NULL,那么两个数合并就是t1(如果t1也为NULL也无所谓,合并之后就是NULL)。
确定单层递归的逻辑:
单层递归的逻辑就比较好写了,这里我们重复利用一下t1这个树,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;
}
}
思路2:迭代-层序遍历
迭代法中,一般一起操作两个树都是使用队列模拟类似层序遍历,同时处理两个树的节点,这种方式最好理解,如果用模拟递归的思路的话,要复杂一些。
将左右节点都放到队列里,都不为空则两个一起放进去,左为空右不为空则右边赋值给左边,左右都为空不用考虑,左不为空右为空也不用考虑(因为本来root1就有了)
代码:
注意区分if和while循环
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(root1);
queue.offer(root2);
while (!queue.isEmpty()) {
TreeNode node1 = queue.poll();
TreeNode node2 = queue.poll();
// 此时两个节点一定不为空,val相加
node1.val = node1.val + node2.val;
// 如果两棵树左节点都不为空,加入队列
if (node1.left != null && node2.left != null) {
queue.offer(node1.left);
queue.offer(node2.left);
}
// 如果两棵树右节点都不为空,加入队列
if (node1.right != null && node2.right != null) {
queue.offer(node1.right);
queue.offer(node2.right);
}
// 若node1的左节点为空,直接赋值
if (node1.left == null && node2.left != null) {
node1.left = node2.left;
}
// 若node1的右节点为空,直接赋值
if (node1.right == null && node2.right != null) {
node1.right = node2.right;
}
}
return root1;
}
}
700.二叉搜索树中的搜索
思路:递归
二叉搜索树是一个有序树:
- 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 它的左、右子树也分别为二叉搜索树
代码:
java代码:
class Solution {
public TreeNode searchBST(TreeNode root, int val) {
if(root==null||root.val==val)return root;
// 判断1,需要有返回值
// if(root.val<val)return searchBST(root.right,val);
// if(root.val>val)return searchBST(root.left,val);
// return null;
// 判断2
if(root.val<val){
return searchBST(root.right,val);
}else{
return searchBST(root.left,val);
}
}
}
思路2:迭代
对于二叉搜索树,不需要回溯的过程,因为节点的有序性就帮我们确定了搜索的方向。
例如要搜索元素为3的节点,我们不需要搜索其他节点,也不需要做回溯,查找的路径已经规划好了。
中间节点如果大于3就向左走,如果小于3就向右走,如图:
class Solution {
public TreeNode searchBST(TreeNode root, int val) {
while(root!=null){
if(root.val==val)return root;
if(root.val<val){
root=root.right;
}else{
root=root.left;
}
}
return null;
}
}
98.验证二叉搜索树
思路:
遇到 搜索树,一定想着中序遍历,这样才能利用上特性。
思路一:中序遍历变成递增数组
确定递归函数,返回值以及参数
要定义一个longlong的全局变量,用来比较遍历的节点是否有序,因为后台测试数据中有int最小值,所以定义为longlong的类型,初始化为longlong最小值。
我们在寻找一个不符合条件的节点,如果没有找到这个节点就遍历了整个树,如果找到不符合的节点了,立刻返回。确定终止条件
如果是空节点 是不是二叉搜索树呢?
是的,二叉搜索树也可以为空!确定单层递归的逻辑
中序遍历,一直更新maxVal,一旦发现maxVal >= root->val,就返回false,注意元素相同时候也要返回false。
思路一代码:定义long最小值做比较
class Solution {
private long prev = Long.MIN_VALUE;
public boolean isValidBST(TreeNode root) {
if(root==null)return true;
boolean left=isValidBST(root.left); // 左
if(root.val>prev){
// 中
prev=root.val;
}else{
return false;
}
boolean right= isValidBST(root.right); // 右
return left&&right;
}
}
代码优化:定义最小值为前一个节点
以上代码是因为后台数据有int最小值测试用例,所以都把maxVal改成了longlong最小值。
如果测试数据中有 longlong的最小值,怎么办?
不可能在初始化一个更小的值了吧。 建议避免 初始化最小值,如下方法取到最左面节点的数值来比较。
class Solution {
private TreeNode pre;
public boolean isValidBST(TreeNode root) {
if(root==null)return true;
boolean left=isValidBST(root.left); // 左
if(!left){
return false;
}
// 中 比较当前节点和前一个节点
if(pre!=null&&root.val<=pre.val){
return false;
}
pre = root;
boolean right= isValidBST(root.right); // 右
return right;
}
}
思路二:统一迭代法
在弹出节点的时候(node==null)时,将这个节点的值和pre比较,如果为true则保存起来,和下一个进行比较
//统一迭代法,也是和前一个节点的最小值比较
class Solution {
public boolean isValidBST(TreeNode root) {
Stack<TreeNode> st = new Stack<>();
TreeNode pre=null;
if (root != null) st.push(root);
while (!st.empty()) {
TreeNode node = st.peek();
if (node != null) {
st.pop(); // 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中
if (node.right!=null) {
// 添加右节点(空节点不入栈)
// if(node.right.val<=node.val){
// return false;
// } 不在这里判断
st.push(node.right);
}
st.push(node); // 添加中节点
st.push(null); // 中节点访问过,但是还没有处理,加入空节点做为标记。
if (node.left!=null){
// 添加左节点(空节点不入栈)
// if(node.left.val>=node.val){
// return false;
// } 不在这里判断
st.push(node.left);
}
} else {
// 只有遇到空节点的时候,才将下一个节点放进结果集
st.pop(); // 将空节点弹出
node = st.peek(); // 重新取出栈中元素
TreeNode temp=st.pop();
if(pre!=null&&pre.val>=temp.val){
return false;
}
pre=temp;
}
}
return true;
}
}