问题

给出如下结构的二叉树:

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. 点击按钮弹出div,留用

    <input type="button" onclick="document.getElementById('div').style.display=(docume ...

  2. 【科研论文】W5100在远程电力质量监测设备中的应用

    摘要: 针对传统电力质量监测方法实时性.多参数测试性能较差的缺点,提出了将以太网接入技术与电能采集相结合进行电力质量现场和远程在线监测的设计方案.硬件设计采用微控制器STM32FI03和以太网控制芯片 ...

  3. codeforces 148D之概率DP

    http://codeforces.com/problemset/problem/148/D D. Bag of mice time limit per test 2 seconds memory l ...

  4. java.net.MulticastSocket Example--reference

    In this example we are going to explain how to use MulticastSocket in Java, in order to enable a ser ...

  5. pbxproj文件冲突解决办法

    企业开发经常会遇到project.pbxproj文件冲突的问题 project.pbxproj文件主要包含了以下几项主要信息 工程文件关联信息,如PBXBuildFile.PBXFileReferen ...

  6. ASP.NET-FineUI开发实践-10

    嵌套Grid,光棍月大放送,不藏着掖着.实在写的不好,没脸藏啊~只考虑显示排序修改什么的都不管! 话说三石官网加实例了,http://fineui.com/demo/#/demo/grid/grid_ ...

  7. AppDomain.CurrentDomain.GetAssemblies()

    AppDomain.CurrentDomain.GetAssemblies() ,获取已加载到此应用程序域的执行上下文中的程序集 解释地址 从微软的解释也可以得知,这个方法只能获取已经加载到此应用程序 ...

  8. java获取字符串格式日期向前或向后n天的日期

    private void setTilteMessage(){          BaseDao dao = new BaseDao();          String titleData = da ...

  9. 开发中遇到的java小知识

    今天在优化一个功能的时候遇到了instr与substr函数,之前没有接触过这两个函数,但是今天无意中用到,一查才发现,真是实用的一对兄弟啊. 先来说说substr函数,这个函数返回的是字符串的一部分. ...

  10. deb文件安装命令

    一般在此类发行版中可以直接双击安装 手动安装.如果您喜欢使用终端,您需要管理员权限来安装一个 .deb 文件. 打开终端后,输入: sudo dpkg -i package_file.deb 要卸载一 ...