笔试题 判定合法二叉树
首先解决留下的笔试题。
如果 n 根绳子可以剪成 m 根长度为 k 的绳子,那一定可以剪成更小的。所以,原问题就转化成了求最后一个满足题意的数。
下面给出核心代码:
bool check(double x)
{
int cnt = 0;
for(auto u : a) cnt += (int)(1.0 * u / x);
return cnt >= m;
}
double l = 0, r = maxn; //maxn表示最长的绳子
while(r - l > eps) //eps为精度,要求保留两位小数通常精度设为1e-4
{
double mid = (l + r) / 2;
if(check(mid)) l = mid;
else r = mid;
}
--------------------------
leetcode331. 验证二叉树的前序序列化
https://leetcode-cn.com/problems/verify-preorder-serialization-of-a-binary-tree/
在遍历二叉树时,我们可以依次记录下节点的值,如果遇到空节点用 # 标记。
现给定一串以逗号分隔的序列,在不构造树的前提下,判断是否是正确的二叉树前序遍历的结果。
样例:
_9_
/ \
3 2
/ \ / \
4 1 # 6
/ \ / \ / \
# # # # # #
输入:"9, 3, 4, #, #, 1, #, #, 2, #, 6, #, #"
输出:true
思路
由于不能建树,所以只能从二叉树的属性入手,寻找足够限制二叉树的条件。下面给出两种思路。
1. 开心消消乐 (栈)
在前序遍历的过程中,第一颗子树被完全遍历时,一定会有这样的结构:
x
/ \
# #
所以我们只要每遇到 "x, #, #" 这样的结构,就把它替换为 “#” ,如果是合法二叉树,最后一定是只剩 “#“,下面模拟一下过程:
整个过程可以用栈来实现:
依次将遇到的节点入栈,每当 ”#“ 入栈时,检查当前栈顶三个元素是否依次为 "#", "#", "x"(不是 # 即可)。我们可以实现一个 merge 函数:
string stk; //使用字符串来模拟栈,方便取栈顶三个元素
int idx; //表示当前栈内有多少元素
void merge()
{
while(idx >= 3 && stk[idx - 1] == '#' &&
stk[idx - 2] == '#' && stk[idx - 3] != '#')
{
stk.pop_back();
stk.pop_back();
stk.pop_back();
stk.push_back('#');
idx -= 2;
}
}
这里使用字符串来模拟栈,方便检查栈顶三个元素,在元素进栈和出栈时维护元素个数 idx 的值。核心代码部分:
bool isValidSerialization(string p) {
int n = p.size();
for(int i = 0; i < n; i++)
{
if(p[i] == '#') stk.push_back(p[i]), idx++, merge();
else if(isdigit(p[i]))
{
while(i < n && isdigit(p[i])) i++;
stk.push_back('0');
idx++;
}
}
return stk == "#";
}
2. 入度与出度
对于一个二叉树,除根结点以外:
每个非空节点提供 1 个入度和 2 个出度;
每个空节点提供 1 个入度。
我们可以在遍历的时候维护一个 diff = out(出度) - in(入度):
每出现一个节点,diff - 1,因为每个节点都会提供一个入度;
若是非空节点 diff + 2,因为提供两个出度。
由于前序遍历中,头节点都是先出现的,因此在整个过程中 diff >= 0,且最后为0。下面给出实现:
bool isValidSerialization(string p) {
int n = p.size();
int d = 1;
for(int i = 0; i < n; i++)
{
if(p[i] == '#') d--;
else if(isdigit(p[i]))
{
if(--d < 0) return false;
while(i < n && isdigit(p[i])) i++;
d += 2;
}
if(d < 0) return false;
}
return d == 0;
}
参考资料:
https://www.hrwhisper.me/leetcode-verify-preorder-serialization-of-a-binary-tree/
举一反三
思考下,如果给出层序遍历的序列,该怎么做呢?
如果不去建树,在层序遍历中难以获得节点间的父子关系,不好用 ”消消乐“ 的方法去做。但在层序遍历中,每颗子树的头节点一定会先被遍历,因此仍然可以用 出度和入度 去维护。
个人感悟
其实,个人在做这道题的时候,一度觉得题面是 稍微有一点点问题的
首先,一个带空节点的合法遍历序列是一定可以唯一确定一颗二叉树的(模拟建树过程就可以了)。
但题目中说,可以是非法的二叉树,那花样可就多了
我们来看这样两个例子:
_1_ _1_
/ \ / \
2 # 2 #
/ \ / \
# # # #
显然,第二个是非法二叉树,但是,这两颗二叉树的前序遍历序列都是 ”1, 2, #, #, #“。一个有可能非法的序列是无法确定一颗二叉树的。
所以,个人觉得题面改成,”判断是否有可能是正确的二叉树的遍历序列“会更好一点。当然,这样就很容易想到用二叉树的属性去维护了。
--------------------------