小R的二叉树探险
在一个神奇的二叉树中,结构非常独特:
每层的节点值赋值方向是交替的,第一层从左到右,第二层从右到左,以此类推,且该二叉树有无穷多层。
小R对这个二叉树充满了好奇,她想知道,在二叉树中两个节点之间x, y的路径长度是多少。
graph TD
1((1));2((2));3((3));4((4));
5((5));6((6));7((7));8((8));
9((9));10((10));11((11));
1---3;1---2;3---4;3---5;
2---6;2---7;6---11;6---10;
7---9;7---8;
测试样例
示例 1:
输入:
x = 11, y = 4
输出:5
示例 1:
输入:
x = 2, y = 5
输出:3
示例 1:
输入:
x = 7, y = 7
输出:0
题目背景
题目描述了一个特殊的二叉树结构,该二叉树有以下特点:
- 具有无限多层。
- 每一层的节点编号有特定的排列方式,层数从1开始:
- 奇数层从左到右编号。
- 偶数层从右到左编号。
小R的问题是:给定二叉树中两个节点 ( x ) 和 ( y ) 的编号,求这两个节点之间的路径长度。
问题分析
对于一个普通的二叉树,我们通常可以通过寻找两个节点的公共祖先来计算路径长度。在标准二叉树中,节点的编号和层次结构都相对简单,但本题中的二叉树结构具有不对称的编号模式,因此需要转换成普通二叉树的结构来简化问题求解。
二叉树的特点总结
-
每一层节点编号从1开始依次增加。例如,假设前几层节点编号如下:
- 第1层:1
- 第2层:3, 2
- 第3层:4, 5, 6, 7
- 第4层:15, 14, 13, 12, 11, 10, 9, 8
-
层次编号的顺序:
- 奇数层从左到右。
- 偶数层从右到左。
根据这些特点,我们发现每一层的节点数都是 ,例如,第 层有 个节点。
路径长度的定义
在普通二叉树中,两个节点之间的路径长度可以通过计算它们到最近公共祖先的距离来确定。设 表示 和 的最近公共祖先,则路径长度为:
解题思路
1. 确定节点的层数
对于任意给定的节点编号 ,首先要确定其所在的层数。可以通过二分法找到最大的 ,使得 成立。这里的 就是节点的层数。
计算层数的步骤如下:
- 设 ,表示第 层的最大编号。
- 通过二分法查找,使得 。
- 得到满足条件的最大的 ,即为节点的层数减一的值。
代码实现如下:
ll cal(ll x) {
return (1ll << x) - 1ll;
}
ll get_depth(ll x) {
ll l = 1, r = x;
while (l < r) {
ll mid = (l + r + 1ll) >> 1;
if (cal(mid) < x) l = mid;
else r = mid - 1;
}
return l;
}
2. 转换节点编号
由于奇数层和偶数层的编号方向不同,因此我们需要将节点编号转换成一种统一的格式,假设所有层均从左至右排列,这样便于后续的父节点关系计算。
具体转换规则如下:
- 如果节点所在层数为奇数层,则编号保持不变。
- 如果节点所在层数为偶数层,则编号按照右到左的顺序重新编号。我们可以通过以下公式将编号转换为从左到右:
代码实现如下:
ll realp(ll x) {
auto dep = get_depth(x);
if (dep & 1) return (1ll << dep) - (x - cal(dep)) + cal(dep) + 1ll;
return x;
}
3. 计算路径长度
在统一编号的情况下,我们可以按照普通二叉树的方法来计算节点之间的距离。具体步骤如下:
- 获取节点 和 的统一编号 和 。
- 不断向上移动较深的节点,直到两个节点到达同一位置,即找到了它们的公共祖先。
- 每次移动计数器增加1,最终计数值即为路径长度。
代码实现如下:
int solution(int x, int y) {
if (x > y) swap(x, y);
ll rx = realp(x), ry = realp(y);
ll ans = 0;
while (rx != ry) {
if (ry > rx) ry >>= 1;
else rx >>= 1;
ans += 1;
}
return ans;
}
测试与验证
本题给出的示例验证了代码的正确性。进一步考虑极限情况,例如节点编号较大时的情况,二分法和移位操作的时间复杂度均为 ( O(\log n) ),可以有效应对大规模数据。
示例验证如下:
int main() {
std::cout << (solution(11, 4) == 5) << std::endl;
std::cout << (solution(2, 5) == 3) << std::endl;
std::cout << (solution(7, 7) == 0) << std::endl;
return 0;
}
复杂度分析
时间复杂度
- 层数计算:二分法计算层数,时间复杂度为 。
- 编号转换:根据层数判断和移位操作,时间复杂度为 。
- 路径长度计算:寻找公共祖先的过程,时间复杂度为 。
综合来看,整体时间复杂度为 。
空间复杂度
由于只需常数空间存储少量变量,因此空间复杂度为 。
总结
本题考察了在非标准结构的二叉树中利用二分查找和层次转换解决路径问题的能力。解题过程中,首先通过二分法计算层数,然后进行编号转换,再利用移位操作实现路径计算。这种解题思路不仅解决了问题,还保证了高效性,符合竞赛题目的要求。
完整的AC代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
ll cal(ll x){
return (1ll<<x)-1ll;
}
ll get_depth(ll x){
ll l = 1 ,r = x;
while(l<r){
ll mid=(l+r+1ll)>>1;
if(cal(mid)<x)l=mid;
else r= mid -1;
}
return l;
}
ll realp(ll x){
auto dep = get_depth(x);
if(dep&1)return (1ll<<(dep))-(x-cal(dep))+cal(dep)+1ll;
return x;
}
int solution(int x, int y) {
if(x>y)swap(x,y);
ll rx = realp(x), ry = realp(y);
ll ans = 0;
while(rx != ry){
if(ry>rx)ry>>=1;
else rx>>=1;
ans+=1;
}
return ans;
}
int main() {
std::cout << (solution(11, 4) == 5) << std::endl;
std::cout << (solution(2, 5) == 3) << std::endl;
std::cout << (solution(7, 7) == 0) << std::endl;
return 0;
}