1.给定一个二叉搜索树,编写一个函数 kthSmallest
来查找其中第 k 个最小的元素。
解:要求找第k个最小的元素,即需要对树进行遍历排序,中序遍历元素得到的结果即为二叉搜索树的排序好的结果,因此采用中序遍历,每遍历一次都保存下父结点并且遍历k次。
代码:
#include <iostream>
#include<queue>
#include<vector>
using namespace std;
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) {
return fun(root,k)->val;
}
TreeNode* fun(TreeNode* root, int& k)
{
TreeNode* target = NULL;
if (root->left != NULL)
{
target = fun(root->left, k);//不断向左子树递归
}
if (target == NULL)//当前没有左叶子结点
{
if (k == 1)
target = root;//记录左叶子结点的父结点
k--;
}
if (target == NULL && root->right != NULL)
{
target = fun(root->right, k);//向右子树遍历
}
return target;
}
};
int main()
{
TreeNode* root =new TreeNode(3);
root->left = new TreeNode(1);
root->right = new TreeNode(4);
root->right->right = new TreeNode(5);
root->left->right = new TreeNode(2);
Solution s1;
cout << s1.kthSmallest(root, 2) << endl;
}
2.给定一个二叉树, 找到该树中两个指定节点的最近公共祖先,一个节点也可以是它自己的祖先。
解:首先,要想通过递归来实现,就需要先确定临界条件,那么临界条件是什么呢?换句话说,临界条件就是递归中能够直接返回的特殊情况,第一点则是最常见的“判空”,判断根结点是否是空节点,如果是,那么肯定就可以马上返回了,这是一个临界条件;再来考虑题意,在以root为根结点的树中找到p结点和q结点的最近公共祖先,那么特殊情况是什么呢?很显然,特殊情况就是根结点就等于q结点或p结点的情况,想一下,如果根结点为二者之一,那么根结点就必定是最近公共祖先了,这时直接返回root即可。由此看来,这道题就一共有三种特殊情况,root == q 、root == p和root==null,这三种情况均直接返回root即可。
根据临界条件,实际上可以发现这道题已经被简化为查找以root为根结点的树上是否有p结点或者q结点,如果有就返回p结点或q结点,否则返回null。从左右子树分别进行递归,即查找左右子树上是否有p结点或者q结点,就一共有4种情况:
第一种情况:左子树和右子树均找没有p结点或者q结点;(这里特别需要注意,虽然题目上说了p结点和q结点必定都存在,但是递归的时候必须把所有情况都考虑进去,因为题目给的条件是针对于整棵树,而递归会到局部,不一定都满足整体条件)
第二种情况:左子树上能找到,但是右子树上找不到,此时就应当直接返回左子树的查找结果;
第三种情况:右子树上能找到,但是左子树上找不到,此时就应当直接返回右子树的查找结果;
第四种情况:左右子树上均能找到,说明此时的p结点和q结点分居root结点两侧,此时就应当直接返回root结点了。
代码:
#include <iostream>
using namespace std;
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) {
return fun(root,k)->val;
}
TreeNode* fun(TreeNode* root, int& k)
{
TreeNode* target = NULL;
if (root->left != NULL)
{
target = fun(root->left, k);//不断向左子树递归
}
if (target == NULL)//当前没有左叶子结点
{
if (k == 1)
target = root;//记录左叶子结点的父结点
k--;
}
if (target == NULL && root->right != NULL)
{
target = fun(root->right, k);//向右子树遍历
}
return target;
}
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if (root == p || root == q || !root) return root;
TreeNode*left = lowestCommonAncestor(root->left, p, q);
TreeNode*right = lowestCommonAncestor(root->right, p, q);
if (!left && !right) return NULL;
else if (left && !right)
return left;
else if (right && !left)
return right;
return root;
}
};
int main()
{
TreeNode* root =new TreeNode(3);
root->left = new TreeNode(1);
root->right = new TreeNode(4);
root->right->right = new TreeNode(5);
root->left->right = new TreeNode(2);
Solution s1;
//cout << s1.kthSmallest(root, 2) << endl;
cout << s1.lowestCommonAncestor(root, root->right->right, root->left)->val << endl;
}
3.序列化是将一个数据结构或者对象转换为连续的比特位的操作,进而可以将转换后的数据存储在一个文件或者内存中,同时也可以通过网络传输到另一个计算机环境,采取相反方式重构得到原数据。请设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 / 反序列化算法执行逻辑,你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。
代码:
#include "pch.h"
#include<string>
#include <iostream>
#include<vector>
using namespace std;
struct TreeNode
{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x) :val(x), left(NULL), right(NULL) {}
};
class Solution {
public:
void serializeCore(TreeNode* root, string & s)
{
if (root==NULL)
{
s += "#";
return;
}
s += to_string(root->val) + " ";
serializeCore(root->left, s);
serializeCore(root->right, s);
}
string serialize(TreeNode* root)
{
string s = "";
serializeCore(root, s);
s.pop_back();
return s;
}
TreeNode* deserializeCore(vector<string> &data, int &len, int &n)
{
TreeNode* node = NULL;
if (data[n]=="#")
{
return node;
}
else {
node = new TreeNode(stoi(data[n]));
}
TreeNode* leftnode = NULL, *rightnode = NULL;
n++;
if (n<=len-2)
{
leftnode = deserializeCore(data, len, n);
n++;
rightnode = deserializeCore(data, len, n);
}
node->left = leftnode;
node->right = rightnode;
return node;
}
TreeNode* deserialize(string data)
{
if (data=="#"||data=="")
{
return NULL;
}
int len = 0;
vector<string> list_s;
string s = "";
for (auto d:data)
{
if (d==' ')
{
list_s.push_back(s);
s = "";
len++;
}
else
s.push_back(d);
}
list_s.push_back(s);
len++;
int n = 0;
TreeNode* root = deserializeCore(list_s, len, n);
return root;
}
};
int main()
{
TreeNode* root =new TreeNode(3);
Solution s1;
root = s1.deserialize("1231342");
}
4.城市的天际线是从远处观看该城市中所有建筑物形成的轮廓的外部轮廓。现在,假设您获得了城市风光照片(图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], ...]
代码:
class Solution {
public:
vector<vector<int>> getSkyline(vector<vector<int>>& buildings) {
if (buildings.empty()) return {};
multiset<pair<int, int>> st;
for (auto b : buildings) {
st.insert(make_pair(b[0], -b[2]));
st.insert(make_pair(b[1], b[2]));
}
vector<vector<int>> ret;
multiset<int> height{0};
int m = 0;
for (auto s : st) {
if (s.second < 0) height.insert(-s.second); // 矩形左侧
else height.erase(height.find(s.second)); // 矩形右侧
if (m != *height.rbegin())
ret.push_back({s.first, *height.rbegin()});
m = *height.rbegin();
}
return ret;
}
};