算法通俗讲解推荐阅读
【算法--链表】83.删除排序链表中的重复元素--通俗讲解
【算法--链表】删除排序链表中的重复元素 II--通俗讲解
【算法--链表】86.分割链表--通俗讲解
【算法】92.翻转链表Ⅱ--通俗讲解
【算法--链表】109.有序链表转换二叉搜索树--通俗讲解
【算法--链表】114.二叉树展开为链表--通俗讲解
通俗易懂讲解“填充每个节点的下一个右侧节点指针”算法题目
一、题目是啥?一句话说清
给你一个完美二叉树,填充每个节点的next指针,使其指向同一层的下一个右侧节点;如果没有下一个节点,则设置为NULL。初始时所有next指针为NULL。
示例:
- 输入:root = [1,2,3,4,5,6,7](完美二叉树)
- 输出:[1,#,2,3,#,4,5,6,7,#](其中'#'表示NULL,即节点1的next为NULL,节点2的next指向3,节点4的next指向5,等等)
二、解题核心
利用当前层已经建立的next指针来遍历和连接下一层的节点。 这就像组织一个会议,先安排第一排的人手拉手(通过next指针),然后第一排的人帮助第二排的人手拉手,依次类推,直到所有排都连接起来。
三、关键在哪里?(3个核心点)
想理解并解决这道题,必须抓住以下三个关键点:
1. 利用已建立的next指针
- 是什么:当前层的next指针已经连接好,我们可以用它来遍历当前层,并连接下一层的节点。
- 为什么重要:这样可以避免使用队列等额外数据结构,实现常数空间复杂度。
2. 分层处理
- 是什么:从根节点开始,一层一层地处理,直到叶子层。
- 为什么重要:每层处理完成后,当前层的next指针就完整了,可以用于连接下一层。
3. 连接不同父节点的子节点
- 是什么:对于当前节点的右子节点,需要将其next指针指向当前节点next节点的左子节点(如果存在)。
- 为什么重要:这是连接不同父节点的子节点的关键,确保同一层所有节点都能通过next指针连接。
四、看图理解流程(通俗理解版本)
让我们用完美二叉树 [1,2,3,4,5,6,7] 的例子来可视化过程:
-
初始化:
- 根节点1的next为NULL。
- 设置leftmost指针指向根节点1。
-
处理第一层(根层):
- 当前层只有节点1,没有next节点,所以不需要连接同一层。
- 但需要连接下一层:节点1有左子节点2和右子节点3。
- 将节点2的next指向节点3。
- 由于节点1没有next节点,节点3的next保持NULL。
- 现在第一层处理完成,第二层的next部分连接:2 → 3
-
处理第二层:
- leftmost指针移动到节点2(第二层最左节点)。
- 使用current指针遍历第二层:从节点2开始。
- 节点2有左子节点4和右子节点5:
- 将节点4的next指向节点5。
- 节点2有next节点(节点3),所以将节点5的next指向节点3的左子节点6。
- 移动到节点3:
- 节点3有左子节点6和右子节点7:
- 将节点6的next指向节点7。
- 节点3没有next节点,所以节点7的next保持NULL。
- 节点3有左子节点6和右子节点7:
- 现在第二层处理完成,第三层的next连接完成:4 → 5 → 6 → 7
-
处理第三层:
- leftmost指针移动到节点4。
- 第三层是叶子层,没有子节点,所以不需要连接下一层。
- 遍历第三层,确认next指针已正确设置。
-
结束:所有层的next指针都已填充。
五、C++ 代码实现(附详细注释)
#include <iostream>
using namespace std;
// 二叉树节点定义
class Node {
public:
int val;
Node* left;
Node* right;
Node* next;
Node() : val(0), left(nullptr), right(nullptr), next(nullptr) {}
Node(int _val) : val(_val), left(nullptr), right(nullptr), next(nullptr) {}
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 nullptr;
}
// 从根节点开始,leftmost用于跟踪每一层的最左节点
Node* leftmost = root;
// 当还有下一层时继续循环
while (leftmost->left != nullptr) {
// 当前层的当前节点
Node* current = leftmost;
// 遍历当前层,连接下一层的节点
while (current != nullptr) {
// 连接相同父节点的子节点
current->left->next = current->right;
// 连接不同父节点的子节点
if (current->next != nullptr) {
current->right->next = current->next->left;
}
// 移动到当前层的下一个节点
current = current->next;
}
// 移动到下一层的最左节点
leftmost = leftmost->left;
}
return root;
}
};
// 辅助函数:打印树的层次结构(通过next指针)
void printTreeByLevel(Node* root) {
Node* levelStart = root;
while (levelStart != nullptr) {
Node* current = levelStart;
while (current != nullptr) {
cout << current->val << " ";
current = current->next;
}
cout << "# "; // 表示层结束
levelStart = levelStart->left; // 移动到下一层
}
cout << endl;
}
// 测试代码
int main() {
// 构建示例完美二叉树:[1,2,3,4,5,6,7]
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->left = new Node(6);
root->right->right = new Node(7);
Solution solution;
solution.connect(root);
printTreeByLevel(root); // 输出:1 # 2 3 # 4 5 6 7 #
// 释放内存(实际面试中可能不需要完整释放)
return 0;
}
六、注意事项
- 完美二叉树假设:题目明确是完美二叉树,所以我们可以安全地访问left和right子节点,无需空值检查。如果不是完美二叉树,这种方法需要调整。
- 空间复杂度:这种方法使用常数额外空间,只用了几个指针变量,优于使用队列的层次遍历(O(n)空间)。
- 指针操作:在连接节点时,确保不会访问空指针。由于是完美二叉树,当前层有子节点时,下一层一定存在。
- 遍历顺序:从左到右遍历当前层,利用next指针移动,效率很高。
- 边界情况:处理空树时直接返回nullptr。