二叉搜索树
它是一棵空树,或者是具有下列性质的二叉树:若它的左子树不空,则左子树上所有节点的值均小于它的根节点的值;若它的右子树不空,则右子树上的所有节点的值均大于它的根节点的值;它的左右子树也分别是二叉搜索树。
二叉搜索树中第K小的元素
给定一个二叉搜索树,编写一个函数 kthSmallest
来查找其中第 k 个最小的元素。
说明:
你可以假设 k 总是有效的,1 ≤ k ≤ 二叉搜索树元素个数。
示例 1:
输入: root = [3,1,4,null,2], k = 1
3
/ \
1 4
\
2
输出: 1
示例 2:
输入: root = [5,3,6,2,4,null,null,1], k = 3
5
/ \
3 6
/ \
2 4
/
1
输出: 3
进阶:
如果二叉搜索树经常被修改(插入/删除操作)并且你需要频繁地查找第 k 小的值,你将如何优化 kthSmallest
函数?
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
int kthSmallest(TreeNode* root, int k) {
//对树进行中序遍历,遍历的同时计数
//好像是不能用递归的方式做吧,那这样就没有办法返回值
//看了别人的做法,也是可以用递归的方式做的,检测到count符合要求的时候对全局变量进行赋值,然后返回即可
stack<TreeNode*> s;
int count=0;
TreeNode* node=root;
while(node!=NULL||!s.empty()){
if(node!=NULL){
s.push(node);
node=node->left;
}
else if(!s.empty()){
TreeNode* temp=s.top();
s.pop();
count++;
if(count==k) return temp->val;
node=temp->right;
}
}
return 0;
}
};
二叉树的最近公共祖先
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4]
示例 1:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1 输出: 3 解释: 节点5
和节点1
的最近公共祖先是节点3。
示例 2:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4 输出: 5 解释: 节点5
和节点4
的最近公共祖先是节点5。
因为根据定义最近公共祖先节点可以为节点本身。
说明:
- 所有节点的值都是唯一的。
- p、q 为不同节点且均存在于给定的二叉树中。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if(root==NULL||root==p||root==q) return root;
else{
TreeNode* left=lowestCommonAncestor(root->left,p,q);
TreeNode* right=lowestCommonAncestor(root->right,p,q);
if(left!=NULL&&right!=NULL) return root;
else if(left==NULL) return right;
else return left;
}
}
};
看到这道题目一开始的想法是,对树进行先序遍历,并且保存着中间的节点,分别得到两个节点的祖先序列,然后比较这两个节点的祖先序列,但是有一个问题是如果使用普通的先序遍历中间节点也会一并弹出,还需要在节点中引入新的变量。
上面的解法是看了其他人的代码:https://www.cnblogs.com/simplepaul/p/7702655.html
也是对树进行遍历,从根节点开始遍历,如果root和p或q其中的一个匹配,那么root就是最低公共子节点,如果不是的话,分别遍历左右子树,如果p和q分别出现在左右子树中,那么root就是最低公共子节点,如果左子树为空,那么最近公共祖先节点出现在右子树,否则出现在左子树。
树的序列化与反序列化
序列化是将一个数据结构或者对象转换为连续的比特位的操作,进而可以将转换后的数据存储在一个文件或者内存中,同时也可以通过网络传输到另一个计算机环境,采取相反方式重构得到原数据。
请设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 / 反序列化算法执行逻辑,你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。
示例:
你可以将以下二叉树:
1
/ \
2 3
/ \
4 5
序列化为 "[1,2,3,null,null,4,5]"
提示: 这与 LeetCode 目前使用的方式一致,详情请参阅 LeetCode 序列化二叉树的格式。你并非必须采取这种方式,你也可以采用其他的方法解决这个问题。
说明: 不要使用类的成员 / 全局 / 静态变量来存储状态,你的序列化和反序列化算法应该是无状态的。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Codec {
public:
// Encodes a tree to a single string.
string serialize(TreeNode* root) {
//使用to_string函数将数字转化为字符串
string res;
queue<TreeNode*> s;
s.push(root);
while(!s.empty()){
TreeNode* temp=s.front();
s.pop();
if(temp==NULL){
//cout<<"res:"<<"null"<<endl;
res.append("null,");
}
else{
// cout<<"res:"<<temp->val<<endl;
res.append(to_string(temp->val));
res.append(",");
s.push(temp->left);
s.push(temp->right);
}
}
return res;
}
// Decodes your encoded data to tree.
TreeNode* deserialize(string data) {
//cout<<"data:"<<data<<endl;
//将字符串进行分割
vector<string> nums;
string temp;
for(int i=0;i<data.size();i++){
if(data[i]!=','){
temp.push_back(data[i]);
}
else{
nums.push_back(temp);
temp="";
}
}
if(nums.size()==0||nums.size()==1) return NULL;
TreeNode* root = new TreeNode(atoi(nums[0].c_str()));
queue<TreeNode*> s;
s.push(root);
int index=1;
while(!s.empty()&&index<nums.size()){
TreeNode* node=s.front();
s.pop();
if(node==NULL) continue;
if(nums[index]=="null"){
node->left=NULL;
}
else{
node->left=new TreeNode(atoi(nums[index].c_str()));
}
if(nums[index+1]=="null"){
node->right=NULL;
}
else{
node->right=new TreeNode(atoi(nums[index+1].c_str()));
}
index+=2;
s.push(node->left);
s.push(node->right);
}
return root;
}
};
// Your Codec object will be instantiated and called as such:
// Codec codec;
// codec.deserialize(codec.serialize(root));
使用层序遍历的方式,借助队列来完成层序遍历,注意为null的时候要如何处理
天际线
城市的天际线是从远处观看该城市中所有建筑物形成的轮廓的外部轮廓。现在,假设您获得了城市风光照片(图A)上显示的所有建筑物的位置和高度,请编写一个程序以输出由这些建筑物形成的天际线(图B)。
每个建筑物的几何信息用三元组 [Li,Ri,Hi]
表示,其中 Li
和 Ri
分别是第 i 座建筑物左右边缘的 x 坐标,Hi
是其高度。可以保证 0 ≤ Li, Ri ≤ INT_MAX
, 0 < Hi ≤ INT_MAX
和 Ri - Li > 0
。您可以假设所有建筑物都是在绝对平坦且高度为 0 的表面上的完美矩形。
例如,图A中所有建筑物的尺寸记录为:[ [2 9 10], [3 7 15], [5 12 12], [15 20 10], [19 24 8] ]
。
输出是以 [ [x1,y1], [x2, y2], [x3, y3], ... ]
格式的“关键点”(图B中的红点)的列表,它们唯一地定义了天际线。关键点是水平线段的左端点。请注意,最右侧建筑物的最后一个关键点仅用于标记天际线的终点,并始终为零高度。此外,任何两个相邻建筑物之间的地面都应被视为天际线轮廓的一部分。
例如,图B中的天际线应该表示为:[ [2 10], [3 15], [7 12], [12 0], [15 10], [20 8], [24, 0] ]
。
说明:
- 任何输入列表中的建筑物数量保证在
[0, 10000]
范围内。 - 输入列表已经按左
x
坐标Li
进行升序排列。 - 输出列表必须按 x 位排序。
- 输出天际线中不得有连续的相同高度的水平线。例如
[...[2 3], [4 5], [7 5], [11 5], [12 7]...]
是不正确的答案;三条高度为 5 的线应该在最终输出中合并为一个:[...[2 3], [4 5], [12 7], ...]
struct node{
int x;
int y;
node(int x,int y){
this->x=x;
this->y=y;
}
};
bool comp(node a,node b){
if(a.x<b.x) return true;
else if(a.x>b.x) return false;
else{
//如果两个数点的横坐标相等的话,要纵坐标大的放在前面
//先增加坐标,再减少坐标
//这也是为什么将前一个点的纵坐标定义为正值,将后一个点的坐标定义为负数的原因
if(a.y<=b.y) return false;
else return true;
}
}
class Solution {
public:
void erase(vector<int> &v,int val){
for(int i=0;i<v.size();i++){
if(v[i]==val){
v.erase(v.begin()+i);
break;
}
}
make_heap(v.begin(),v.end(),less<int>());
}
void merge(vector<node> &rectangle){
for(int i=0;i<rectangle.size()-1;){
if(i<0) i=0;
if(abs(rectangle[i].x)==abs(rectangle[i+1].x)&&abs(rectangle[i].y)==abs(rectangle[i+1].y)){
rectangle.erase(rectangle.begin()+i+1);
rectangle.erase(rectangle.begin()+i);
i=i-1;
}
else{
i++;
}
}
}
vector<vector<int>> getSkyline(vector<vector<int>>& buildings) {
vector<int> v;
vector<vector<int> > res;
if(buildings.size()==0) return res;
vector<node> rectangle;
for(int i=0;i<buildings.size();i++){
rectangle.push_back(node(buildings[i][0],buildings[i][2]));
rectangle.push_back(node(buildings[i][1],-buildings[i][2]));
}
sort(rectangle.begin(),rectangle.end(),comp);
v.push_back(0);
make_heap(v.begin(),v.end(),less<int>());
for(int i=0;i<rectangle.size();i++){
int max=v[0];
if(rectangle[i].y>0){
v.push_back(rectangle[i].y);
push_heap(v.begin(),v.end(),less<int>());
if(max!=v[0]){
vector<int> temp;
temp.push_back(rectangle[i].x);
temp.push_back(v[0]);
res.push_back(temp);
}
}
if(rectangle[i].y<0){
erase(v,-rectangle[i].y);
if(max!=v[0]){
vector<int> temp;
temp.push_back(rectangle[i].x);
temp.push_back(v[0]);
res.push_back(temp);
}
}
}
return res;
}
};
参考别人的思路完成的代码,但是在最后一个很长的用例上超时了。
将每一个矩形转化为左上角和右上角的两个点,为标记左上角和右上角,将左上角的点的纵坐标设为负数,右上角的点的纵坐标设为正数,维护一串数(堆),保存在当前这里的所有点的最大高度。
将这些点按照横坐标从小到大排序,遇到横坐标相等的情况时,将纵坐标更大的排在前面。
如果遇到一个矩形的起始点,将它的纵坐标的绝对值加到这个堆中,看最大值是否发生变化,如果发生变化,则此处添加一个关键点,遇到纵坐标为负数(矩形的右上角)则将这个点的纵坐标删除,看堆中的最大值有没有发生变化。
这道题目可能会出现的临界值包括两栋建筑物紧挨着,高度相等或不相等;没有建筑物;建筑物交叠着,但是没有紧挨着,高度相等或不相等。这些情况都需要考虑到。