114Flatten Binary Tree to Linked List 二叉树到单链表的扁平化处理

来源:互联网 发布:centos没有桌面 编辑:程序博客网 时间:2024/05/16 02:39

原题地址: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的值。结合前面提到的前序,我们可以处理根结点,然后再递归处理左子树、右子树,该解法详见后面解法一。然而,这种解法貌似太过平常了,万一面试官又说出了惯用的“不许用递归”的约束怎么办,为此,参考别人的思路,找到了第二种解法,使用循环而非递归。具体思路详见解法二。

解法一

算法描述

  1. 分配一个地址空间last,用来存储最后访问的结点的地址
  2. 分配一个临时结点tmp,将其地址&tmp存放在last中
  3. 判断下一个待处理的结点是否为空,为空则退出;否则进入4
  4. 将当前结点root挂在last存储的地址指向的结点的右结点(*last)->right上;并将(*last)->left置空
  5. last更新,存储4中root的地址
  6. 递归处理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

这样看来,问题就变得很简单了,找到一棵树最右下角的叶子结点也是比较轻松的。

算法描述

  1. 处理当前结点current,如果current为空,则算法结束;否则跳到2
  2. 如果current左孩子为空,则current指向->right,调到1处继续;否则跳到3
  3. 找到左子树current->left的最右下角的结点p,把current->right挂在p->right上,把current->left挂在current->right上,然后current->left置空;跳到4
  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

0 0
原创粉丝点击