题目链接:Equilibrium Mobile
题目描述:
给定一颗二叉树,该二叉树由括号序列来描述例如 [ [ 3 , 7 ] , 6 ] [[3,7],6] [[3,7],6]表示根节点左右儿子均不为空,并且根节点的左儿子的左右儿子均不为空,数字代表的是各个叶子结点的重量。再例如 [ [ 2 , 3 ] , [ 4 , 5 ] ] [[2,3],[4,5]] [[2,3],[4,5]]代表的二叉树如下:
现在你可以修改各个叶子结点的重量,问最少修改几个叶子结点的重量可以让二叉树平衡(平衡是指二叉树的每个子树的左右子树的重量均相等)。例如对于上图,可以将除了 2 2 2以外的叶子结点全部修改成 2 2 2这样修改 3 3 3次二叉树就会平衡。
注意:输入包含多组测试样例。
题解:
我们设叶子结点的个数为 n n n,那么实际上我们很容易的发现,修改次数一定是小于 n n n,同时当我们确定任意一个叶子结点的重量后,要让树达到平衡,那么其他叶子结点的重量均可以计算出来。也就是说如果一个叶子结点的重量确定,那么其他叶子结点的重量也就确定,并且只有唯一的可能。这是因为,确定任意一个叶子结点之后,该叶子结点的兄弟重量就确定了,兄弟结点的重量确定,兄弟结点的后代重量也就确定了,而父亲的重量是两个儿子重量之和,这也意味着可以无限重复上述过程来确定所有叶子结点的重量。所以对于本题,我们可以枚举每个叶子结点,用其来推断其他叶子结点的重量,然后记录修改的叶子结点个数最后再取最小值即可。
上述过程可以找出最终结点,但是会进行多次遍历的过程,如何只进行一次遍历就求出最终的解呢?实际上我们只需要在遍历的时候,计算出各个叶节点不变时根节点的重量然后用一个计数器来保存各个重量下的叶结点个数,这个数代表什么呢?实际上如果两个叶节点不变对应的根节点的重量相同,那么这两个叶节点可以同时不被修改(跟上述的结论类似,如果根节点的重量确定了,那么各个叶结点的重量也就确定了,结论可以推广为:任意一个结点的重量确定,那么其他结点的重量也就随之唯一确定),所以 n − c n t n-cnt n−cnt就是需要修改的次数。
如何确定根节点的重量?设根节点的深度为 0 0 0,叶结点的深度为 d e p t h depth depth那么根节点的重量为: w × 2 d e p t h w\times 2^{depth} w×2depth,其中 w w w为叶结点的重量。
下面考虑如何进行树的遍历。可以发现没遇到一个左括号就代表新的一层开始了,都需要向下进行一次,而遇到数字时则代表到达了叶结点,只需要进行更新即可,当遇到右括号时则代表这一层结束了,此时应该向上返回,逗号意味着在同一层,继续遍历即可。
代码:
#include <bits/stdc++.h>
using namespace std;
int t, n, ans, len;
string treeString;
map<long long, int> cnt;
// i表示当前处于字符串中位置
// depth表示当前的深度
// 返回值为下一次应该搜索的位置
int dfs(int i, int depth)
{
while(i < len) {
if (treeString[i] == '[') {
i = dfs(i + 1, depth + 1);
} else if (treeString[i] == ']') {
return i + 1;
} else if (treeString[i] == ',') {
i++;
} else {
// 当前位置是一个数字,读取该叶节点的重量
long long weight = 0;
do {
weight = weight * 10 + treeString[i] - '0';
i++;
} while(i < len && isdigit(treeString[i]));
n++;
cnt[weight << depth]++;
}
}
return len;
}
int main()
{
ios::sync_with_stdio(false);
cin >> t;
while(t--) {
cin >> treeString;
n = 0; // 叶子结点个数
len = treeString.length();
cnt.clear();
dfs(0, 0);
ans = n;
for (auto kv : cnt) {
ans = min(ans, n - kv.second);
}
cout << ans << endl;
}
return 0;
}