没错,著名的翻转二叉树它来了,它带着难倒homebrew作者Max Howell1的步伐走来了——
虽然是个段子,而且这题也确实很简单,但是由于我好久没做LeetCode了,忘了LeetCode的示例和实际输入是两回事,光看着群友的截图我以为输入数据是一个数组= =于是随便写了这么个解法,顺便复健一下十几年前学过的C😄
题目参考这里。
用数据结构本身来解决问题
考虑输入数据为二叉树的先序遍历
t
t
t的场景。对于任意第
i
i
i层,该层节点必然处于
t
[
2
i
−
1
]
∼
t
[
2
i
−
1
]
t[2^{i-1}]\sim t[2^{i}-1]
t[2i−1]∼t[2i−1]的位置上。与在树上的、层间位置无关,每个节点只在它所在的层改变其本身的位置。因此,只需考察每一节点在树翻转前后其在层内的位置如何变化即可。
显然,对任意层
l
l
l,给每个该层节点分配一个临时序号
l
1
,
l
2
,
.
.
.
,
l
m
(
m
=
2
l
−
1
)
l_1,l_2,...,l_m (m=2^{l-1})
l1,l2,...,lm(m=2l−1),那么该层的任意一个节点
l
k
l_k
lk在翻转至
l
k
′
l_{k'}
lk′时,必有
k
+
k
′
=
m
+
1
k+k'=m+1
k+k′=m+1
因此,只要在数组上对这两个节点做交换即可直接得出翻转后的二叉树的先序遍历。
由节点序号定位层
已知
∀
k
,
∃
i
,
2
i
−
1
≤
k
≤
2
i
−
1
\forall k, \exist i, 2^{i-1}\le k\le 2^i-1
∀k,∃i,2i−1≤k≤2i−1是显然的,但是如何通过
k
k
k快速求
k
k
k所在的区间?
注意到不等号两边均接近2的幂,考虑通过
k
k
k的二进制表示直接通过位运算定位区间:
int getPower(int num) {
int i=1;
while (i<=num) i<<=1;
return i;
}
则 2 i = m = g e t P o w e r ( k ) , k ∈ [ m / 2 , m − 1 ] 2^i=m=getPower(k), k\in[m/2, m-1] 2i=m=getPower(k),k∈[m/2,m−1]。
对每层进行翻转
使用两个指针翻转每一层。
设指针
a
,
b
a,b
a,b分别指向当前层的起点(
2
i
−
1
2^{i-1}
2i−1)和终点(
2
i
−
1
2^i-1
2i−1),交换后各向中间移动一步;直至
a
>
b
a>b
a>b。之后,需要将
a
a
a移至下一层起点(
2
i
2^i
2i,即getPower(a)),
b
b
b为下一层终点(
2
i
+
1
−
1
2^{i+1}-1
2i+1−1):
while (a<n) {
if (a<b) {
swap(&t[a], &t[b]);
a++;b--;
} else {
a=getPower(a);
b=(a<<1)-1;
}
}
加上输入输出的处理部分,代码即完成:
#include <stdio.h>
#include <stdlib.h>
int getPower(int num) {
int i=1;
while (i<=num) i<<=1;
return i;
}
void swap(int* a, int* b) {
int t=*a;
*a=*b;
*b=t;
}
int main(int argc, char* argv[]) {
int *t, n=getPower(argc-1);
t=(int *)calloc(getPower(n), sizeof(int));
for (int i=1;i<argc;i++) {
t[i] = atoi(argv[i]);
}
int a=2,b=3;
while (a<n) {
if (a<b) {
swap(&t[a], &t[b]);
a++;b--;
} else {
a=getPower(b);
b=(a<<1)-1;
}
}
for (int i=1;i<n;i++) {
printf("%d ", t[i]);
}
return 0;
}
时间效率
O
(
n
)
O(n)
O(n),空间效率
T
(
n
)
T(n)
T(n)。
实际上只对一半数据做了处理以及没有递归出入栈的操作,应该比遍历树快吧!还是得看输入数据的数据格式。