问题

给出如下结构的二叉树:

struct TreeLinkNode {

TreeLinkNode *left;

TreeLinkNode *right;

TreeLinkNode *next;

}

填充每一个next指针使其指向自己的右边邻居节点。如果没有右边的邻居节点,next指针须设成NULL。

在开始时,所有的next指针被初始化成NULL。

注意:

  • 你只能使用常数级别的额外空间
  • 你可以假设该树为完全二叉树(即所有叶子节点都在同一层,而且每个父节点都有两个子节点)。

例如,给出如下完全二叉树:

1

/  \

2     3

/ \   /  \

4   5  6  7

在调用你的函数后,树看起来将是这样:

1 -> NULL

/    \

2 -> 3 -> NULL

/ \    /  \

4->5->6->7 -> NULL

初始思路

要得到每个节点的右边邻居,显然应该对二叉树进行层次遍历。使用队列+循环是进行二叉树层次遍历的标准方法。但是这里我们有一点特别的需求-需要知道每一层的结束,因为每一层最右边节点的next指针需要设为null。回想我们在 [LeetCode 126] - 单词梯II(Word Ladder II) 中最后提到的双vector交替循环法,在这里特别适用:遍历当前vector得到的子节点都放到下一个vector中,当遍历完毕时,下一个vector中恰好就是下一层的所有节点。我们只需将其中每个元素的next指针指向自己的下一个元素即可,当然,最后一个元素要跳过。然后清空当前vector,交换当前vector和下一个vector(通过对下标取反)并重复此步骤直到当前vector为空就完成了任务。最后完成的代码如下:

 class Solution {
public:
void connect(TreeLinkNode *root)
{
if(!root)
{
return;
} candidates_[].clear();
candidates_[].clear(); int flag = ; candidates_[flag].push_back(root); while(!candidates_[flag].empty())
{
for(auto iter = candidates_[flag].begin(); iter != candidates_[flag].end(); ++iter)
{
TreeLinkNode* node = *iter;
if(node->left)
{
candidates_[!flag].push_back(node->left);
}
if(node->right)
{
candidates_[!flag].push_back(node->right);
}
} if(candidates_[!flag].empty())
{
break;
} for(auto iter = candidates_[!flag].begin(); iter != candidates_[!flag].end() - ; ++iter)
{
(*iter)->next = *(iter + );
} candidates_[flag].clear();
flag = !flag;
}
} private:
std::vector<TreeLinkNode*> candidates_[];
};

connect

上面的代码能通过Judge Small和Judge Large的测试,但是仔细审题后可以发现,其实我们的方案并不符合题目的要求-你只能使用常数级别的额外空间。使用队列对二叉树进行层次遍历,队列需要的最大空间是和二叉树的大小有关的,对题目中的完全二叉树来说,它等于叶子节点的个数。即对层数为n(n>0)的二叉树,需要的空间为2^(n-1)级别。

改进方案

要符合题目的要求,看来是不能使用循环遍历队列的方式来层次遍历二叉树了。遍历二叉树,除了循环无非就是递归。对层次遍历来说,递归的方法并不是那么常用,那么,让我们来看看用递归怎么层次遍历二叉树。

想象一下我们要访问一个二叉树的第n层,我们会从顶点出发,一层层往下直到第n层。如果把一次往下走的这个动作看作一次函数调用,那么我们就可以得到一串递归调用。这个递归的结束条件是什么?从顶点往下一层后,还需要走n-1层能达到我们的目标,再下一层,需要n-2层。如此往复,当n=1时就走到了我们期望的层数。由此n=1就是递归结束的条件。用一个三层的完全二叉树来模拟递归访问第三层,情况如下:

Visit(node1, 3)

1

Visit(node1->left, 2) /                                                               \ Visit(node1->right, 2)

2                                                                  3

Visit(node2->left, 1) /   \ Visit(node2->right, 1) Visit(node3->left, 1)  /   \ Visit(node3->right, 1)

4      5                                                             6      7

由此,我们得到伪代码如下:

访问二叉树(节点,层数)

如果层数=1,访问当前节点

否则

访问二叉树(节点的左子树,层数-1)

访问二叉树(节点的右子树,层数-1)

现在我们得到了递归访问二叉树第n层的方法,但是离访问所有层还是有一段距离。要访问所有层,我们需要让n从1开始递增,循环调用访问二叉树第n层的递归函数。什么时候不需要再递增n了呢?不需要再递增层数意味着我们的n已经走到了二叉树的底层,根据题目中完全二叉树的条件,底层也就是叶子节点所在的层。而叶子节点的特性-左右子节点皆为null就为我们提供了检查的标准。由此我们就需要在上面两个对左右子树的递归调用中各返回一个指针,当返回的两个指针皆为null时,说明我们到达底层了。符合递归结束条件时,自然是返回当前节点,那递归过程中返回哪个指针?其实这里在左右指针中随便选一个就行了,因为我们只需要利用返回值是否为空指针这个信息,而对完全二叉树来说左右子节点的这个性质必然是一致的。

最后,不要忘了我们的目的是要为每个节点设置next指针,所以我们需要一个成员变量来保存左边邻居。每当访问到一个节点,我们将左边邻居的next指针指向它,然后将它的地址拷贝到保存左邻居的指针中。当然,不要忘了要做一些特殊判断来处理每层第一个节点的情况。

 class Solution {
public:
void connect(TreeLinkNode *root)
{
if(!root)
{
return;
} hasToTheEnd_ = false; int level = ; while(!hasToTheEnd_)
{
nextLeft_ = nullptr;
VisitLevel(root, level);
++level;
}
} private:
TreeLinkNode* VisitLevel(TreeLinkNode* node, int level)
{
if(level == )
{
if(nextLeft_ != nullptr)
{
nextLeft_ ->next = node; }
nextLeft_ = node;
return node;
} TreeLinkNode* left = VisitLevel(node->left, level - );
TreeLinkNode* right = VisitLevel(node->right, level - ); if(left == nullptr && right == nullptr)
{
hasToTheEnd_ = true;
} //完全二叉树,左右子树的非空性必然一致,随便返回一个即可
return left; } TreeLinkNode* nextLeft_;
bool hasToTheEnd_;
};

connect_v2

现在的代码就完全符合题目要求并且能通过大小数据集测试了。

后继问题

如果给出的树为任意二叉树,前面的解决方案还能工作吗?

后继思路

如果去掉了完全二叉树这个条件,那么我们用来判断是否到达二叉树底层的方法就不再生效了,如下面的二叉树:

1

/     \

2       3

/   \

4     5

可以看到编号为3的叶子节点并不是最底层。

我们怎么知道3号节点并不是最底层?因为和它在同一层的2号节点还存在子节点。那么我们可以更改一下判断最底层的条件:如果当前访问的层有任一节点存在子节点,说明当前层不是最底层。什么时候我们走到了当前访问的层?n = 1递归条件结束的时候。我们可以在这里判断当前节点是否存在子节点,如果有就设置继续循环标志。也就是说,我们把判断当前层是否为最底层的逻辑由“如果符合某条件,则终止循环”变为了“如果符合某条件,则继续循环”。因此相应的我们需要在完成针对一层的循环后将标志复位。做了上面的修改后,可以发现递归函数的返回值不再需要了,因为我们在最后一次递归调用返回前就完成了判断而不再通过返回信息供上层函数判断。得到的最终代码如下,现在处理任意二叉树都没有问题了:

  class Solution{
public:
void connect(TreeLinkNode *root)
{
if(!root)
{
return;
} hasToTheEnd_ = false; int level = ; while(!hasToTheEnd_)
{
hasToTheEnd_ = true;
nextLeft_ = nullptr;
VisitLevel(root, level);
++level;
}
} private:
void VisitLevel(TreeLinkNode* node, int level)
{
if(level == )
{
if(nextLeft_ != nullptr)
{
nextLeft_ ->next = node; }
nextLeft_ = node; if(node->left != nullptr || node->right != nullptr)
{
hasToTheEnd_ = false;
}
} if(node->left)
{
VisitLevel(node->left, level - );
} if(node->right)
{
VisitLevel(node->right, level - );
} } TreeLinkNode* nextLeft_;
bool hasToTheEnd_;
};

connect_117

[LeetCode 116 117] - 填充每一个节点的指向右边邻居的指针I & II (Populating Next Right Pointers in Each Node I & II)的更多相关文章

  1. leetcode@ [116/117] Populating Next Right Pointers in Each Node I & II (Tree, BFS)

    https://leetcode.com/problems/populating-next-right-pointers-in-each-node-ii/ Follow up for problem ...

  2. LeetCode:Populating Next Right Pointers in Each Node I II

    LeetCode:Populating Next Right Pointers in Each Node Given a binary tree struct TreeLinkNode { TreeL ...

  3. 【leetcode】Populating Next Right Pointers in Each Node I & II(middle)

    Given a binary tree struct TreeLinkNode { TreeLinkNode *left; TreeLinkNode *right; TreeLinkNode *nex ...

  4. LeetCode 116/117. 填充同一层的兄弟节点(Populating Next Right Pointers in Each Node)

    题目描述 给定一个二叉树 struct TreeLinkNode { TreeLinkNode *left; TreeLinkNode *right; TreeLinkNode *next; } 填充 ...

  5. [Java]LeetCode117. 填充同一层的兄弟节点 II | Populating Next Right Pointers in Each Node II

    Given a binary tree struct TreeLinkNode { TreeLinkNode *left; TreeLinkNode *right; TreeLinkNode *nex ...

  6. LeetCode 题解:Populating Next Right Pointers in Each Node I & II 二有难度。考虑不全面。

    每次应该把root同层的右侧节点传过来.如果没有,就传NULL. 同时,应该是先右后左. 感觉这次的代码还挺简洁的.. void construct(struct TreeLinkNode *root ...

  7. Java for LeetCode 117 Populating Next Right Pointers in Each Node II

    Follow up for problem "Populating Next Right Pointers in Each Node". What if the given tre ...

  8. leetcode 199. Binary Tree Right Side View 、leetcode 116. Populating Next Right Pointers in Each Node 、117. Populating Next Right Pointers in Each Node II

    leetcode 199. Binary Tree Right Side View 这个题实际上就是把每一行最右侧的树打印出来,所以实际上还是一个层次遍历. 依旧利用之前层次遍历的代码,每次大的循环存 ...

  9. [LeetCode] 116. Populating Next Right Pointers in Each Node 每个节点的右向指针

    You are given a perfect binary tree where all leaves are on the same level, and every parent has two ...

随机推荐

  1. Maven搭建环境(Linux& Windows)

    Linux下安装Maven 1.前提条件: 1)下载并安装好JDK .在终端输入命令“java -version”,如果出现类似如下信息说明JDK安装成功. $ java -version java ...

  2. Java语言与JVM中的Lambda表达式全解

    Lambda表达式是自Java SE 5引入泛型以来最重大的Java语言新特性,本文是2012年度最后一期Java Magazine中的一篇文章,它介绍了Lamdba的设计初衷,应用场景与基本语法. ...

  3. L - Kakuro Extension - HDU 3338 - (最大流)

    题意:有一个填数字的游戏,需要你为白色的块内填一些值,不过不能随意填的,是有一些规则的(废话),在空白的上方和作方给出一些值,如果左下角有值说明下面列的和等于这个值,右上角的值等于这行后面的数的和,如 ...

  4. (1.1.9)UVA 10930 A-Sequence(模拟)

    /* * UVA_10930_1.cpp * * Created on: 2013年10月7日 * Author: Administrator */ #include <iostream> ...

  5. 比较了一下基于PhoneGAP/JQ Mobile 等基于HTML5的Phone 开发框架

    比较了一下基于PhoneGAP/JQ Mobile 等基于HTML5的Phone 开发框架,如果做APP客户端的化,想达到Native UI的效果,都是胡扯的,根本不可能. PhoneGAP 如果想达 ...

  6. 实用bootstrap 表格控件

    http://wenzhixin.net.cn/p/bootstrap-table/docs/examples.html

  7. [AngularJS] Services, Factories, and Providers -- Service vs Factory

    Creating a Service: Before actual create an angular service, first create a constructor in Javascrip ...

  8. Sort List (使用归并排序的链表排序)

    Sort a linked list in O(n log n) time using constant space complexity. C++代码的实现: #include<iostrea ...

  9. Android组件间的数据传输

    组件我们有了,那么我们缺少一个组件之间传递信息的渠道.利用Intent做载体,这是一个王道的做法.还有呢,可以利用文件系统来做数据共享.也可以使用Application设置全局数据,利用组件来进行控制 ...

  10. Java学习之路(一)了解Java

    Java“白皮书”的关键术语 1)简单性 相对于C++:没有头文件.指针运算.结构.联合.操作符重载.虚基类. 另一方面是小:java微型版(Java Micro Edition)用于嵌入式设备 2) ...