写在前面
二叉树和链表转化的题主要分为两类,即将二叉树转化成单链表或者双向链表,和单链表转化成二叉树,而这里的二叉树往往指的是BST-tree,而单链表的序列往往是BST-tree的中序序列,往往像这类题,需要用到树的递归遍历来做,我形象的称它为「 借力打力」,就是说,按照它的函数形式递归解题即可,比如二叉树转单链表的题(题144),使树flat的方式有多,比如单向牵引(题144),双向牵引后root节点更新为最左的节点成为链表头节点(这个是《剑指offer》书上的题),转化过程如下图。
114. 二叉树展开为链表
1
/ \
2 5
/ \ \
3 4 6
1
\
2
\
3
\
4
\
5
\
6
解题思路: 看懂题目样例基本就看懂了题目所谓的in-place转化的大致过程,其特征是,将将左子树和右子树分别flat,然后将左子树插入到root与右子树的中间空挡。起初思路走岔了,按照《剑指offer》那题解,进入last指针,但是后来发现直接在原题形式上解是最方便的,last指针由flat之后的链表扫描到末尾节点求得,然后在用last参与链接的拼接。下面也把《剑指offer》那题贴出来解一下,对比着看。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
void flatten(TreeNode* root) {
if (root == nullptr) return;
flatten(root->left);
flatten(root->right);
TreeNode *last = root->left;
if (last == nullptr) return;
while (last->right) last = last->right;
TreeNode *tmp = root->right;
root->right = root->left;
root->left = nullptr;
last->right = tmp;
}
};
剑指 Offer 36. 二叉搜索树与双向链表
解题思路: 此题因为是转换之后的是双向链表,所以我选择附设两个指针pre和last分别指向转换之后的链表的头节点和尾节点,不过依稀记得《剑指offer》上给出的解答是附设了一个指针last。本题的思路比较直接,先分别对左右子树进行转换,并带回相应的pre和last指针,然后利用左右子树的pre和last指针调整root节点位置并进行链表的拼接,完成之后要更新root节点对应的链表pre和last的位置(相当于带回转换的BST的pre和last),这里在处理题114时作者对叶子节点左单独的判断,i.e.,当为叶子节点时,pre和last直接指向root(叶子节点),否则则根据节点左、右子树的相应pre和last更新之,但是在处理此题的时,可以直接先将pre和last指向root节点(先默认假设其为叶子节点),然后在根据左、右子树是否存在,相应更新之。同种方法本质是相同的,但是,针对叶子节点需要做单独判断,因为这个是pre和last指针更新的起点(否则它们会直接为空)。
/*
// Definition for a Node.
class Node {
public:
int val;
Node* left;
Node* right;
Node() {}
Node(int _val) {
val = _val;
left = NULL;
right = NULL;
}
Node(int _val, Node* _left, Node* _right) {
val = _val;
left = _left;
right = _right;
}
};
*/
class Solution {
public:
Node* treeToDoublyList(Node* root) {
Node *pre = NULL, *last = NULL;
helper(root, pre, last);
if (pre) pre->left = last;
if (last) last->right = pre;
return pre;
}
void helper(Node *root, Node* &pre, Node* &last) {
if (root == NULL) {
last = NULL;
return;
}
Node *preLeft = NULL, *lastLeft = NULL;
Node *preRight = NULL, *lastRight = NULL;
helper(root->left, preLeft, lastLeft);
helper(root->right, preRight, lastRight);
root->left = lastLeft;
if (lastLeft) lastLeft->right = root;
root->right = preRight;
if (preRight) preRight->left = root;
pre = last = root;
if (root->left) pre = preLeft;
if (root->right) last = lastRight;
}
};
————————————————————
总结树转链表题的通用解法:
- 用两个附设指针pre和last掐住(或者说箍住、限定住)链表的范围(或者说头尾),转换完毕后,再利用左、右子树对应的两组pre和last指针进行链表拼接、root节点前、后项指针的修改。
————————————————————