117. 填充每个节点的下一个右侧节点指针 II
文章目录
一、题目
给定一个二叉树:
struct Node {
int val;
Node *left;
Node *right;
Node *next;
}
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL
。
初始状态下,所有 next 指针都被设置为 NULL
。
示例 1:
输入:root = [1,2,3,4,5,null,7]
输出:[1,#,2,3,#,4,5,7,#]
解释:给定二叉树如图 A 所示,你的函数应该填充它的每个 next 指针,以指向其下一个右侧节点,如图 B 所示。序列化输出按层序遍历顺序(由 next 指针连接),'#' 表示每层的末尾。
示例 2:
输入:root = []
输出:[]
提示:
- 树中的节点数在范围
[0, 6000]
内 -100 <= Node.val <= 100
进阶:
- 你只能使用常量级额外空间。
- 使用递归解题也符合要求,本题中递归程序的隐式栈空间不计入额外空间复杂度。
二、题解
方法一:迭代
算法思路:
我们可以使用广度优先搜索(BFS)的方法来遍历二叉树,并在遍历过程中为每个节点的next
指针赋值。我们使用一个双端队列(deque)来辅助进行BFS,将每层的节点依次加入队列中,并使用队列中的节点来建立next
指针的连接关系。
具体实现:
以下是算法的具体实现步骤:
- 首先,我们创建一个双端队列
deq
来存储待处理的节点。如果根节点为空,直接返回根节点。 - 将根节点添加到
deq
中。 - 使用循环遍历队列
deq
,在每一轮中,我们处理当前队列的所有节点,这代表一层的节点。为了区分每一层,我们需要在每轮开始时记录当前队列的大小,将其保存为size
。 - 创建一个
prev
指针,用于跟踪前一个节点,初始时设为nullptr
。 - 在循环中,我们取出队列中的节点,并分别进行如下操作:
- 如果
prev
不为空,将prev->next
指向当前节点cur
,建立连接关系。 - 将当前节点
cur
赋值给prev
,更新prev
指针为当前节点。 - 如果当前节点的左子节点不为空,将左子节点加入队列。
- 如果当前节点的右子节点不为空,将右子节点加入队列。
- 如果
- 完成当前层的处理后,继续下一轮循环,处理下一层的节点。
- 重复步骤5和步骤6,直到队列
deq
为空,即所有节点的next
指针都连接完成。 - 返回根节点。
/*
// Definition for a Node.
class Node {
public:
int val;
Node* left;
Node* right;
Node* next;
Node() : val(0), left(NULL), right(NULL), next(NULL) {}
Node(int _val) : val(_val), left(NULL), right(NULL), next(NULL) {}
Node(int _val, Node* _left, Node* _right, Node* _next)
: val(_val), left(_left), right(_right), next(_next) {}
};
*/
class Solution {
public:
Node* connect(Node* root) {
deque<Node*> deq;
if(root == nullptr) return root;
deq.push_back(root);
while(!deq.empty()){
int size = deq.size();
Node* prev = nullptr;
for(int i = 0; i < size; i++){
Node *cur = deq.front();
deq.pop_front();
if(prev != nullptr) prev->next = cur;
prev = cur;
if(cur->left) deq.push_back(cur->left);
if(cur->right) deq.push_back(cur->right);
}
}
return root;
}
};
算法分析:
- 时间复杂度:BFS遍历二叉树的时间复杂度是O(N),其中N是二叉树节点的数量。
- 空间复杂度:由于我们使用了一个双端队列来存储待处理节点,队列中的节点数量最多不会超过一层的节点数量,而树的最大宽度不会超过N/2。因此,队列的空间复杂度是O(N/2),即O(N)。除了队列之外,我们没有使用其他与节点数量相关的额外数据结构,因此总的空间复杂度也是O(N)。
方法二:递归
算法思路:
- 首先,我们可以观察到,每个节点的左子节点的
next
指针都可以很容易地连接到右子节点,因为它们在同一层。 - 对于每个节点的右子节点,它的
next
指针可以通过父节点的next
指针连接到父节点的右侧节点的最左子节点,或者跨越到父节点的右侧节点的左子节点的最左子节点。 - 通过这种方式,我们可以逐层连接节点的
next
指针,直到遍历完整个树。
具体实现:
为了实现这一思路,我们使用一个辅助函数findNextRight
来查找一个节点的右侧节点。然后在connect
函数中,我们先连接每个节点的左右子节点,然后再递归地连接右子树和左子树。
具体步骤如下:
- 首先判断根节点是否为空,如果为空直接返回
nullptr
。 - 如果根节点有左子节点,我们先尝试将其左子节点连接到右子节点。如果根节点的右子节点存在,我们直接将左子节点的
next
指针连接到右子节点。否则,我们调用findNextRight
函数来查找根节点的右侧节点,然后将左子节点的next
指针连接到右侧节点的最左子节点。 - 如果根节点有右子节点,我们调用
findNextRight
函数来查找根节点的右侧节点,然后将右子节点的next
指针连接到右侧节点的最左子节点。 - 然后我们递归地先连接右子树再连接左子树,保证在连接左子树时右子树已经连接好了。
- 最后返回根节点。
class Solution {
public:
Node* connect(Node* root) {
if (root == nullptr) return nullptr;
if (root->left) {
if (root->right) {
root->left->next = root->right;
} else {
root->left->next = findNextRight(root->next);
}
}
if (root->right) {
root->right->next = findNextRight(root->next);
}
connect(root->right); // 注意先连接右子树再连接左子树
connect(root->left);
return root;
}
private:
Node* findNextRight(Node* node) {
while (node) {
if (node->left) return node->left;
if (node->right) return node->right;
node = node->next;
}
return nullptr;
}
};
算法分析:
- 时间复杂度:假设二叉树中有n个节点,则遍历整个树需要O(n)的时间复杂度。
- 空间复杂度:空间复杂度取决于树的形状,最好的情况下是O(logn),最坏的情况下是O(n)。