代码随想录第十七天 | Leecode 654. 最大二叉树、617. 合并二叉树、700. 二叉搜索树中的搜索、98. 验证二叉搜索树
Leecode 654. 最大二叉树
题目描述
给定一个不重复的整数数组 nums
。 最大二叉树 可以用下面的算法从 nums
递归地构建:
创建一个根节点,其值为 nums
中的最大值。
递归地在最大值 左边 的 子数组前缀上 构建左子树。
递归地在最大值 右边 的 子数组后缀上 构建右子树。
返回 nums
构建的 最大二叉树 。
- 示例 1:
输入:nums = [3,2,1,6,0,5]
输出:[6,3,5,null,2,0,null,null,1]
- 示例 2:
输入:
nums = [3,2,1]
输出:[3,null,2,null,1]
递归建立最大二叉树
本题和昨天做过的构建二叉树的题目很像,甚至相比之下会更简单,构造树所需传入的数组只有一个。可以大致写出迭代算法的思路如下:
- 首先确定终止条件:当前数组为空时,则返回空指针
- 处理当前节点:找出数组中最大值的所在序号,根据最大值建立新节点
- 递归处理左右子节点
- 返回当前节点
根据上面思路可以得到代码如下:
class Solution {
public:
TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
if(nums.empty()) return nullptr; // 如果当前数组为空,则直接返回空指针
int rootIndex = 0; // 初始化根节点序号
for(int i = 0; i < nums.size(); i++){ // 遍历查找数组中最大值的序号,作为根节点的值
if(nums[i] > nums[rootIndex]) rootIndex = i;
}
TreeNode* root = new TreeNode(nums[rootIndex]); // 新建节点,用最大数建立根节点
vector<int> leftVec(nums.begin(), nums.begin() + rootIndex); // 将最大数左侧的数组作为新的数组,并递归建立左子树
root->left = constructMaximumBinaryTree(leftVec);
vector<int> rightVec(nums.begin()+rootIndex+1, nums.end()); // 将最大数右侧的数组作为新的数组,并递归建立右子树
root->right = constructMaximumBinaryTree(rightVec);
return root; // 返回根节点
}
};
Leecode 617. 合并二叉树
题目描述
给你两棵二叉树: root1
和 root2
。
想象一下,当你将其中一棵覆盖到另一棵之上时,两棵树上的一些节点将会重叠(而另一些不会)。你需要将这两棵树合并成一棵新二叉树。合并的规则是:如果两个节点重叠,那么将这两个节点的值相加作为合并后节点的新值;否则,不为 null
的节点将直接作为新二叉树的节点。
返回合并后的二叉树。
注意: 合并过程必须从两个树的根节点开始。
- 示例 1:
输入:root1 = [1,3,2,5]
,root2 = [2,1,3,null,4,null,7]
输出:[3,4,5,5,4,null,7]
- 示例 2:
输入:
root1 = [1]
,root2 = [1,2]
输出:[2,2]
递归合并二叉树
本题使用递归进行合并,重点在于讨论终止条件:
- 如果当前两棵树的当前节点都为空,则返回空
- 如果有一棵树的当前节点为空,则直接返回另一棵树的根节点;后续子节点也相当于直接嫁接过来
- 如果都不为空,则当前节点为两棵树的节点值相加。并需要对当前节点的左右子节点进行递归
class Solution {
public:
TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
if(!root1 && !root2 )return nullptr; // 都为空返回空
if(!root1) return root2; // 一个为空,直接嫁接另一棵树
if(!root2) return root1;
TreeNode* root = new TreeNode(root1->val + root2->val); // 都不为空,则需要将两棵树当前节点相加
root->left = mergeTrees(root1->left, root2->left); // 递归合并左右子树
root->right = mergeTrees(root1->right, root2->right);
return root;
}
};
Leecode 700. 二叉搜索树中的搜索
题目描述
给定二叉搜索树(BST)的根节点 root
和一个整数值 val
。
你需要在 BST 中找到节点值等于 val
的节点。 返回以该节点为根的子树。 如果节点不存在,则返回 null
。
- 示例 1:
输入:root = [4,2,7,1,3]
,val = 2
输出:[2,1,3]
- 示例 2:
输入:root = [4,2,7,1,3]
,val = 5
输出:[]
递归法进行查找
在二叉树中进行查找非常方便快捷,只需要从根节点开始,如果值比当前节点更大;则往右子树查找,如果值更小则往左子树查找;但如果一直找到空节点都没有相等,则返回空。可以写出代码如下:
class Solution {
public:
TreeNode* searchBST(TreeNode* root, int val) {
if(!root) return nullptr;
if(val > root->val) return searchBST(root->right, val);
if(val < root->val) return searchBST(root->left, val);
return root;
}
};
Leecode 98. 验证二叉搜索树
题目描述
给你一个二叉树的根节点 root
,判断其是否是一个有效的二叉搜索树。
有效 二叉搜索树定义如下:
节点的左子树只包含 小于 当前节点的数。
节点的右子树只包含 大于 当前节点的数。
所有左子树和右子树自身必须也是二叉搜索树。
- 示例 1:
输入:root = [2,1,3]
输出:true
- 示例 2:
输入:root = [5,1,4,null,null,3,6]
输出:false
解释:根节点的值是5
,但是右子节点的值是4
。
使用递归进行验证
对于本题,首先需要明确二叉搜索树的定义,对于每一个节点,都必须满足比左子树中最大的节点更大,比右子树中最小的节点更小。需要特别注意的是,这里提到的最大和最小的节点,都并不一定直接是左右子节点,在不清楚二叉搜索树的定义的情况下,非常容易混淆这一点。如果仅仅只考虑每一个节点比左子节点大,比右子节点小,那么很容易写成下面这样:
// 错误示范
class Solution {
public:
void isValidHelper(TreeNode* root, bool& isValid){
if(!root || !isValid) return;
if(root->left) {
if(root->left->val >= root->val){ // 仅与左子节点比较,正确应当是与左子树中的最大节点比较
isValid = false;
return;
}
isValidHelper(root->left, isValid);
}
if(root->right) {
if(root->right->val <= root->val){ // 仅与右子节点比较,正确应与右子树中的最小节点比较
isValid = false;
return;
}
isValidHelper(root->right,isValid);
}
return;
}
bool isValidBST(TreeNode* root) {
bool isValid = true;
isValidHelper(root, isValid);
return isValid;
}
};
上面代码无法通过测试算例,而把这段代码写下来也是为了给自己加深印象(因为我第一反应就是这样写的)。
正确的思路应当是采用中序遍历,实际上对于二叉搜索树的中序遍历,正好就是排序好的序列。所以只需要记录该二叉数的中序遍历序列,再判断序列是否满足单调递增性,即可判断该序列是否为二叉搜索树。但是,如果采用先建立中序遍历,后续再遍历一遍中序遍历是否满足单调递增性,所消耗的时间复杂度:中序遍历$O(\log n) + \(遍历数组\)O(n)\(,故总时间复杂度为\)O(n)\(。而如果可以在进行中序遍历的同时就能判断是否为二叉搜索树,就可以省略后续时间复杂度为\)O(n)$的遍历。因此我们可以得到代码如下:
class Solution {
public:
void inOrder(TreeNode* curNode, vector<int>& orderVec, bool& result){ // 辅助函数,进行中序遍历,同时也要判断是否满足二叉搜索树
if(!curNode || !result) return; // 如果当前节点为空,或者已经确定不是二叉搜索树,直接返回
if(curNode->left) inOrder(curNode->left, orderVec, result); // 如果左子节点存在,则对左子节点递归调用
if(orderVec.size() > 0 && orderVec[orderVec.size()-1] >= curNode->val) { // 将当前节点与于已经记录的中序序列最后一个元素进行比较;如果记录为空,则不比较
result = false; // 如果当前节点不大于左子树中最大节点,说明不是二叉搜索树,将result设置为false
return;
}
orderVec.push_back(curNode->val); // 如果当前节点更大,则将当前节点记录到vector中
if(curNode->right) inOrder(curNode->right, orderVec, result); // 对右子树递归调用
return;
}
bool isValidBST(TreeNode* root) {
bool result = true; // 初始化result为真,后续判断如果不满足再变为false,如果遍历结束都还满足则为真
vector<int> orderVec; // 初始化空的Vector,用于存放
inOrder(root, orderVec, result); // 中序遍历,同时判断是否满足二叉搜索树的条件
return result; // 返回结果
}
};
上面判断代码的时间复杂度为\(O(n)\)。
今日总结
今天题目都比较简单,加深了对于二叉搜索树的定义的熟悉。
今天力扣刷到64题了,再接再厉!
代码随想录第十七天 | Leecode 654. 最大二叉树、617. 合并二叉树、700. 二叉搜索树中的搜索、98. 验证二叉搜索树的更多相关文章
- 代码随想录算法训练营day20 | leetcode ● 654.最大二叉树 ● 617.合并二叉树 ● 700.二叉搜索树中的搜索 ● 98.验证二叉搜索树
LeetCode 654.最大二叉树 分析1.0 if(start == end) return节点索引 locateMaxNode(arr,start,end) new root = 最大索引对应节 ...
- 代码随想录算法训练营day22 | leetcode 235. 二叉搜索树的最近公共祖先 ● 701.二叉搜索树中的插入操作 ● 450.删除二叉搜索树中的节点
LeetCode 235. 二叉搜索树的最近公共祖先 分析1.0 二叉搜索树根节点元素值大小介于子树之间,所以只要找到第一个介于他俩之间的节点就行 class Solution { public T ...
- 代码随想录第十三天 | 150. 逆波兰表达式求值、239. 滑动窗口最大值、347.前 K 个高频元素
第一题150. 逆波兰表达式求值 根据 逆波兰表示法,求表达式的值. 有效的算符包括 +.-.*./ .每个运算对象可以是整数,也可以是另一个逆波兰表达式. 注意 两个整数之间的除法只保留整数部分. ...
- 代码随想录第八天 |344.反转字符串 、541. 反转字符串II、剑指Offer 05.替换空格 、151.翻转字符串里的单词 、剑指Offer58-II.左旋转字符串
第一题344.反转字符串 编写一个函数,其作用是将输入的字符串反转过来.输入字符串以字符数组 s 的形式给出. 不要给另外的数组分配额外的空间,你必须原地修改输入数组.使用 O(1) 的额外空间解决这 ...
- 代码随想录-day1
链表 今天主要是把链表专题刷完了,链表专题的题目不是很难,基本都是考察对链表的操作的理解. 在处理链表问题的时候,我们通常会引入一个哨兵节点(dummy),dummy节点指向原链表的头结点.这样,当我 ...
- 代码随想录 day0 博客怎么写
前言 2.25日开始记录自己的博客生涯以及代码随想录训练营的每日内容 一.题目链接怎么找?怎么设置连接? 力扣题目链接1:力扣 二.正文怎么写? 二分查找 算法思路: 二分查找需要保证数组为有序数组同 ...
- 【LeetCode动态规划#05】背包问题的理论分析(基于代码随想录的个人理解,多图)
背包问题 问题描述 背包问题是一系列问题的统称,具体包括:01背包.完全背包.多重背包.分组背包等(仅需掌握前两种,后面的为竞赛级题目) 下面来研究01背包 实际上即使是最经典的01背包,也不会直接出 ...
- 代码随想录算法训练营day13
基础知识 二叉树基础知识 二叉树多考察完全二叉树.满二叉树,可以分为链式存储和数组存储,父子兄弟访问方式也有所不同,遍历也分为了前中后序遍历和层次遍历 Java定义 public class Tree ...
- leecode刷题(30)-- 二叉树的后序遍历
leecode刷题(30)-- 二叉树的后序遍历 二叉树的后序遍历 给定一个二叉树,返回它的 后序 遍历. 示例: 输入: [1,null,2,3] 1 \ 2 / 3 输出: [3,2,1] 思路 ...
- leecode刷题(29)-- 二叉树的中序遍历
leecode刷题(29)-- 二叉树的中序遍历 二叉树的中序遍历 给定一个二叉树,返回它的中序 遍历. 示例: 输入: [1,null,2,3] 1 \ 2 / 3 输出: [1,3,2] 思路 跟 ...
随机推荐
- HBuilder X对vue的支持有多强?
HBuilder X对vue的支持有多强? 分类:HBuilder Vue HBuilderX中使用vue,如果是打开vue文件,会自动挂载vue语法库.如果是HTML文件里引用vue框架,需要点 ...
- springboot+easypoi模板导出Excel 动态表头+多表格(一个sheet)
1.需求:将此页面的几个表格导出 其中表头中的仓库 集散地是是动态生成的. 首先制作Excel模板: 代码: @Resource private RedisService redisService; ...
- 解决黑群晖 Docker 日志八小时时间差的有效方法
步骤一:登录黑群晖控制台 首先,我们需要登录到黑群晖控制台.可以通过SSH登录,或是直接在黑群晖控制台界面上操作. 步骤二:停止相关的Docker容器 在解决时间差问题之前,我们需要停止相关的Dock ...
- Typecho复制文章自带版权说明
自带版权说明代码 <script> document.body.addEventListener('copy', function (e) { if (window.getSelectio ...
- 如何用爱思助手给苹果iPhone手机免越狱修改虚拟定位教程
使用爱思助手修改定位的方法: 1.在电脑上下载安装 爱思助手 客户端,并更新到最新版,用数据线将苹果移动设备连接到电脑. 2.连接成功后,依次打开爱思助手"工具箱 - 虚拟定位". ...
- Kali Linux(202104)重置root账户密码
1.前言 如果忘记了Kali Linux系统的登录密码,最关键的需求就是重置root用户的登录密码, 之后使用root账户可以修改其他账户的密码. 因此, 本文就介绍一下在不知道root用户登录密码的 ...
- 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本地AI Agent
一.天价邀请码VS开源革命:打工人今夜无眠 昨夜科技圈被两个关键词刷屏:Manus激活码炒至5万元5,7,GitHub神秘项目OpenManus突然开源6,7.这场戏剧性对决的背后,是一场关于「A ...
- 写于vue3.0发布前夕的helloworld之二
接着,继续走,来到了vm.$mount. 开始生成render函数,生成VNode,由于是第一次加载,所以patch机制为只删除前一个dom节点机制,下面都会讲到. 先到$mount: Vue.pro ...
- some notes
.displaynone { display: none } https://voce.chat/zh-CN 一个开源的迷你的国产开源聊天软件,服务端非常小,只有 15MB. 4 分钟前 虽然没有办法 ...
- nginx集群同步方案
之前公司同事写过rsync加触发nginx reload脚本,适合nginx配置内容完全一致的情况. 今天写一个同步指定文件的脚本,修改完主服务器.使用scp传输到其他nginx服务器上重启NGINX ...