原题地址:https://leetcode.com/problems/flatten-binary-tree-to-linked-list/
题目描述
Given a binary tree, flatten it to a linked list in-place.
给定一个二叉树,在已有的空间内进行扁平化生成一个单链表.
For example,
例如,
Given
给出
1
/ \
2 5
/ \ \
3 4 6
The flattened tree should look like:
扁平化处理后的树应该看起来如下:
1
\
2
\
3
\
4
\
5
\
6
解题思路
通过观察可以发现,扁平化的过程是与前序遍历的过程极其类似的。这也是一个动态规划的问题。我们可以很自然的想到一种解决方法,用一个内存last记录上一个遍历到的结点,然后每遍历一个点,都把它链接到last上,然后更新last的值。结合前面提到的前序,我们可以处理根结点,然后再递归处理左子树、右子树,该解法详见后面解法一。然而,这种解法貌似太过平常了,万一面试官又说出了惯用的“不许用递归”的约束怎么办,为此,参考别人的思路,找到了第二种解法,使用循环而非递归。具体思路详见解法二。
解法一
算法描述
- 分配一个地址空间last,用来存储最后访问的结点的地址
- 分配一个临时结点tmp,将其地址&tmp存放在last中
- 判断下一个待处理的结点是否为空,为空则退出;否则进入4
- 将当前结点root挂在last存储的地址指向的结点的右结点(*last)->right上;并将(*last)->left置空
- last更新,存储4中root的地址
- 递归处理root的左子树、右子树
在这里,需要注意的一点是,在递归处理的时候,由于需要更改某结点的左右指针,因此其原有的结构关系会被改变,因此在递归处理前,我们需要暂存一下左右指针。举例如下:
初始情况:
1
/ \
2 3
第一步处理后有:
tmp
\
1
/ \
2 3
这一步结束后,last指向1,然后该处理2
第二步处理后有:
tmp
\
1
\
2
这时候last指向1,而由于在这一步中,1的right指向了2,原先1->right指向3的关系被打破了。于是3就被丢掉了。这一步结束后,last指向2.
因此,我们在递归调用前,需要先暂存一下当前结点原先的左右结点地址,保证不会错误的丢掉结点。我们在后面的代码中暂存了左右结点的地址,不过实际上,如上描述的情况之后发生在结点的右结点关系中,我们只暂存右结点地址即可。
代码
/**
* 递归扁平化处理二叉树
* input last : 一个固定内存,用于存储上一个访问的结点的地址
* input root : 当前要处理的二叉树的根结点
*/
void flattenRecursive(struct TreeNode** last, struct TreeNode* root) {
if (root == NULL) return;
(*last)->right = root; // 将root挂在*last存储的地址指向的右结点上
(*last)->left = NULL; // 将*last所存储的地址指向的左结点置空
*last = root; // 更新最后一个结点信息,last存储当前结点的地址
// 暂存一下root的左右结点的地址,因为在递归调用中root的左右结点的值会被更改
struct TreeNode* left = root->left, * right = root->right; // 实际上left不用暂存
// 递归,扁平化处理左子树
flattenRecursive(last, left);
// 递归,扁平化处理右子树
flattenRecursive(last, right);
}
/**
* 扁平化处理二叉树
*/
void flatten(struct TreeNode* root) {
if (root == NULL) return; // 空树直接返回
// 一个固定内存,用于存储上一个访问的结点的地址
struct TreeNode** last = (struct TreeNode**)malloc(sizeof(struct TreeNode*));
struct TreeNode tmp; // 为了更统一的处理,使用一个临时变量,初始时把其地址存储在last中
*last = &tmp; // 这样root会挂在tmp.right上
// 递归扁平化处理
flattenRecursive(last, root);
// 释放内存
free(last);
}
完整代码 https://github.com/Orange1991/leetcode/blob/master/114/c/main.c
运行情况
Status:Accept
Time:4ms
解法二
面试官经常会问到一些思路独到的解决方法,确实每个人都能想到的方法并不那么能说明什么。解法一中的递归思路,基本上每个人一看到动态规划都能想出来。那不一样的方法是怎么来的呢?我认为应该先观察题目,找到问题的本质。我们来看一个最简单的例子:
1 1
/ \ -----> \
2 3 2
\
3
简单来说,就是把根结点的左子树和右子树全都调整到它的右子树上。也就是说,对于每个结点,经过我们的调整后,它只有右结点;那么如果根结点没有左结点的话,根本不用调整啊(对于这个根结点来说),问题就变成了调整这个结点的右结点(划归为子问题,动态规划);如果根结点有左结点呢?那就调整它的左结点,将其插入到右结点之间(对,是插入,这里有前序要求)。
好,我们用图示来描述目前的思路:
(1)如果当前结点没有左结点,则跳过此结点,变为调整右结点的问题
1 <--- current 1
\ \
2 -----> 2 <--- current
/ \ / \
tree tree tree tree
(2)如果当前结点有左结点,则将其左结点插入到其与右结点之间
1 1
\ \
2 <--- current 2 <--- current
/ \ -----> \
ltree rtree ltree
\
rtree
那么问题来了,如何把ltree插入到root与rtree之间呢?
问题的关键点是,rtree的所有结点都晚于ltree的所有结点被处理。对于前序遍历来说,一棵树最先被处理的是根结点root,最后被处理的结点是root的最右下角的结点(原谅这个不严谨但是很形象的说法)。那么,我们可以欣喜的发现,只要把rtree挂在ltree中最后被处理的结点的右结点上就可以啦!于是整个过程变成了如下所示:
(1)如果当前结点没有左结点,则跳过此结点,变为调整右结点的问题
1 <--- current 1
\ \
2 -----> 2 <--- current
/ \ / \
tree tree tree tree
(2)如果当前结点有左结点,则先将其右结点挂到左子树的最右下角的叶子结点上,然后把左结点移到右结点上
1 1 1
\ \ \
2 <--- current 2 <--- current 2 <--- current
/ \ -----> / -----> \
ltree rtree ltree ltree
\ \
rtree rtree
这样看来,问题就变得很简单了,找到一棵树最右下角的叶子结点也是比较轻松的。
算法描述
- 处理当前结点current,如果current为空,则算法结束;否则跳到2
- 如果current左孩子为空,则current指向->right,调到1处继续;否则跳到3
- 找到左子树current->left的最右下角的结点p,把current->right挂在p->right上,把current->left挂在current->right上,然后current->left置空;跳到4
- current指向current->right,跳到1继续。
代码
/**
* 扁平化处理二叉树
*/
void flatten(struct TreeNode* root) {
struct TreeNode* p;
while (root != NULL) { // root不为空,则继续调整
if (root->left) { // 如果当前结点有左子树才调整
p = root->left;// 指向左结点
while (p->right) p = p->right; // 指向左子树最右下角的结点
p->right = root->right; // 把当前结点的右子树挂在左子树的最右下角结点的右结点上
root->right = root->left; // 当前结点的左子树挂在右结点上
root->left = NULL; // 当前结点的左子树置空
} // 调整结束后,当前结点左结点为空,右结点不一定
root = root->right; // 指向当前结点的右结点,继续调整
}
}
完整代码 https://github.com/Orange1991/leetcode/blob/master/114/c2nd/main.c
运行情况
Status:Accept
Time:4ms
测试数据
输入
[-6,8,-4,8,-5,-1,null,-9,9,8,8,null,null,-5,6,null,null,null,-4,null,4,null,null,8,8,null,null,null,5,null,null,null,null,null,-9]
输出
-6->8->8->-9->-5->6->8->8->9->-5->8->-4->8->4->5->-9->-4->-1->null
// sfg1991@163.com
// 2015/6/19