通俗算法讲解推荐阅读:
【算法--链表】83.删除排序链表中的重复元素--通俗讲解
【算法--链表】删除排序链表中的重复元素 II--通俗讲解
【算法--链表】86.分割链表--通俗讲解
【算法】92.翻转链表Ⅱ--通俗讲解
【算法--链表】109.有序链表转换二叉搜索树--通俗讲解
【算法--链表】114.二叉树展开为链表--通俗讲解
【算法--链表】116.填充每个节点的下一个右侧节点指针--通俗讲解
通俗易懂讲解“填充每个节点的下一个右侧节点指针Ⅱ”算法题目
一、题目是啥?一句话说清
给定一个二叉树,填充每个节点的next指针,使其指向同一层的下一个右侧节点;如果找不到下一个节点,则next指针为NULL。初始时所有next指针都是NULL。
示例:
- 输入:二叉树(可能不完整,即不是满二叉树)
- 输出:二叉树 with next pointers filled
二、解题核心
使用层次遍历,但不需要使用队列,而是利用已建立的next指针来遍历下一层。我们使用一个虚拟节点(dummy)来帮助构建下一层的链表,然后用当前层的next指针来访问所有节点,同时连接下一层的节点。
这就像在每一层中,我们有一个链表,我们遍历这个链表来处理每个节点,同时将它们的子节点连接到下一层的链表中,从而节省空间。
三、关键在哪里?(3个核心点)
想理解并解决这道题,必须抓住以下三个关键点:
1. 利用当前层的next指针遍历
- 是什么:从根节点开始,当前层已经通过next指针连接起来,所以我们可以像遍历链表一样遍历当前层。
- 为什么重要:这样可以避免使用队列,节省空间。我们不需要存储所有节点,只需要几个指针。
2. 构建下一层的链表
- 是什么:在处理当前层的每个节点时,我们将它的左子节点和右子节点依次添加到下一层的链表中。使用一个tail指针来维护下一层链表的尾部。
- 为什么重要:这样我们可以按顺序连接下一层的节点,为下一层的遍历做准备,并保持节点的顺序。
3. 虚拟节点(Dummy Node)的运用
- 是什么:为下一层创建一个虚拟头节点,这样我们可以轻松地访问下一层的头节点。
- 为什么重要:虚拟节点简化了链表操作,避免了处理空链表的情况。当下一层没有节点时,虚拟节点的next为NULL,我们可以停止循环。
四、看图理解流程(通俗理解版本)
假设我们有这样一个二叉树:
1
/ \
2 3
/ \ \
4 5 7
我们需要填充next指针。
- 初始化:从根节点开始,当前
current指向节点1。 - 第一层处理:
- 创建虚拟节点
dummy,tail指向dummy。 - 遍历当前层(只有节点1):
- 节点1有左子节点2:将
tail.next指向节点2,tail移动到节点2。 - 节点1有右子节点3:将
tail.next指向节点3,tail移动到节点3。
- 节点1有左子节点2:将
- 当前层遍历完后,
current设置为dummy.next(即节点2)。
- 创建虚拟节点
- 第二层处理:
- 当前
current指向节点2(第二层的头)。 - 创建新的虚拟节点
dummy,tail指向dummy。 - 遍历当前层(节点2和节点3通过next连接):
- 节点2有左子节点4:
tail.next指向节点4,tail移动到节点4。 - 节点2有右子节点5:
tail.next指向节点5,tail移动到节点5。 - 节点3有右子节点7:
tail.next指向节点7,tail移动到节点7。
- 节点2有左子节点4:
- 当前层遍历完后,
current设置为dummy.next(即节点4)。
- 当前
- 第三层处理:
- 当前
current指向节点4(第三层的头)。 - 创建虚拟节点
dummy,tail指向dummy。 - 遍历当前层(节点4、5、7通过next连接):
- 节点4没有子节点,跳过。
- 节点5没有子节点,跳过。
- 节点7没有子节点,跳过。
- 下一层为空,循环结束。
- 当前
最终next指针:
- 节点1的next为NULL。
- 节点2的next指向节点3。
- 节点3的next为NULL。
- 节点4的next指向节点5。
- 节点5的next指向节点7。
- 节点7的next为NULL。
五、C++ 代码实现(附详细注释)
#include <iostream>
using namespace std;
// 二叉树节点定义
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) {
if (root == nullptr) return root;
Node* current = root; // 当前层的头节点
while (current != nullptr) {
Node dummy(0); // 虚拟头节点用于下一层
Node* tail = &dummy; // tail用于构建下一层的链表
// 遍历当前层
while (current != nullptr) {
if (current->left != nullptr) {
tail->next = current->left;
tail = tail->next;
}
if (current->right != nullptr) {
tail->next = current->right;
tail = tail->next;
}
current = current->next; // 移动到当前层的下一个节点
}
current = dummy.next; // 移动到下一层的头节点
}
return root;
}
};
// 辅助函数:打印层次链表(用于测试)
void printLevels(Node* root) {
Node* levelStart = root;
while (levelStart != nullptr) {
Node* current = levelStart;
while (current != nullptr) {
cout << current->val << " ";
current = current->next;
}
cout << "#" << endl;
// 找到下一层的起始点:由于next指针已填充,但下一层的起始点需要通过第一个节点的左子节点或右子节点?
// 实际上,我们无法直接知道下一层的起始点,但算法中是通过虚拟节点获取的。这里为了演示,我们假设从当前层第一个节点的左子节点开始(如果有),否则右子节点?
// 但这不是通用的,因为下一层可能从任意节点开始。所以通常测试时,我们只打印已知的层次。
levelStart = levelStart->left; // 这可能不准确,但对于简单二叉树可行
}
}
// 测试代码
int main() {
// 构建示例二叉树
Node* root = new Node(1);
root->left = new Node(2);
root->right = new Node(3);
root->left->left = new Node(4);
root->left->right = new Node(5);
root->right->right = new Node(7);
Solution solution;
solution.connect(root);
// 打印层次
cout << "Level 1: ";
Node* current = root;
while (current) {
cout << current->val << " ";
current = current->next;
}
cout << "#" << endl;
cout << "Level 2: ";
current = root->left;
while (current) {
cout << current->val << " ";
current = current->next;
}
cout << "#" << endl;
cout << "Level 3: ";
current = root->left->left;
while (current) {
cout << current->val << " ";
current = current->next;
}
cout << "#" << endl;
return 0;
}
六、时间空间复杂度
- 时间复杂度:O(n),其中n是节点数。每个节点被访问一次。
- 空间复杂度:O(1),只使用了常数额外的空间(几个指针),不包括递归栈空间(因为这里是迭代解法)。
七、注意事项
- 虚拟节点的使用:每次外层循环都创建一个新的虚拟节点(在栈上),这是安全的,因为它是局部变量。
- 移动指针:在内层循环中,我们移动current指针遍历当前层,直到NULL,确保不遗漏节点。
- 连接顺序:在添加子节点时,先左后右,保持从左到右的顺序。
- 空树处理:如果根节点为空,直接返回。
- 二叉树可能不完整:这个算法适用于任何二叉树,因为我们不依赖完整的结构,而是按顺序连接子节点。
通俗算法讲解推荐阅读:
【算法--链表】83.删除排序链表中的重复元素--通俗讲解
【算法--链表】删除排序链表中的重复元素 II--通俗讲解
【算法--链表】86.分割链表--通俗讲解
【算法】92.翻转链表Ⅱ--通俗讲解
【算法--链表】109.有序链表转换二叉搜索树--通俗讲解
【算法--链表】114.二叉树展开为链表--通俗讲解
【算法--链表】116.填充每个节点的下一个右侧节点指针--通俗讲解