题目
114. 二叉树展开为链表
我的思路
本题有两个关键点:把树变为以前序方式遍历的单链表&原地算法
参考官方题解,结合自己的理解整理几种思路:
思路一:
先序遍历一遍树,把节点依次存储在一个队列中。再遍历该队列,依次把队列的下一个元素赋值给当前元素的右孩子。
时间复杂度和空间复杂度均为n(树的节点数)。
注意:先序遍历可以递归实现,也可以迭代实现(利用栈)!!
class Solution { public: void flatten(TreeNode* root) { auto v = vector<TreeNode*>(); auto stk = stack<TreeNode*>(); TreeNode *node = root; while (node != nullptr || !stk.empty()) { while (node != nullptr) { v.push_back(node); stk.push(node); node = node->left; } node = stk.top(); stk.pop(); node = node->right; } int size = v.size(); for (int i = 1; i < size; i++) { auto prev = v.at(i - 1), curr = v.at(i); prev->left = nullptr; prev->right = curr; } } }; 作者:LeetCode-Solution 链接:https://leetcode-cn.com/problems/flatten-binary-tree-to-linked-list/solution/er-cha-shu-zhan-kai-wei-lian-biao-by-leetcode-solu/ 来源:力扣(LeetCode) 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
思路二:
思路一进行了把先序遍历原树和生成最终的链表分为两步进行,是否能在一次遍历中完成?遍历左子树并进行修改之前,就保留右节点的信息。
时间复杂度和空间复杂度均为n(树的节点数)。
这种做法最好用迭代(利用)实现先序遍历。
class Solution { public: void flatten(TreeNode* root) { if (root == nullptr) { return; } auto stk = stack<TreeNode*>(); stk.push(root); TreeNode *prev = nullptr; while (!stk.empty()) { TreeNode *curr = stk.top(); stk.pop(); if (prev != nullptr) { prev->left = nullptr; prev->right = curr; } TreeNode *left = curr->left, *right = curr->right; if (right != nullptr) { stk.push(right); } if (left != nullptr) { stk.push(left); } prev = curr; } } }; 作者:LeetCode-Solution 链接:https://leetcode-cn.com/problems/flatten-binary-tree-to-linked-list/solution/er-cha-shu-zhan-kai-wei-lian-biao-by-leetcode-solu/ 来源:力扣(LeetCode) 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
思路三:
是否可以不利用辅助存储空间呢?观察可知。
如果一个节点的左子节点为空,则该节点不需要进行展开操作。如果一个节点的左子节点不为空,则该节点的左子树中的最后一个节点被访问之后,该节点的右子节点被访问。该节点的左子树中最后一个被访问的节点是左子树中的最右边的节点,也是该节点的前驱节点。因此,问题转化成寻找当前节点的前驱节点。
具体做法是,对于当前节点,如果其左子节点不为空,则在其左子树中找到最右边的节点,作为前驱节点,将当前节点的右子节点赋给前驱节点的右子节点,然后将当前节点的左子节点赋给当前节点的右子节点,并将当前节点的左子节点设为空。对当前节点处理结束后,继续处理链表中的下一个节点,直到所有节点都处理结束。
时间复杂度是n,空间复杂度是1。
class Solution { public: void flatten(TreeNode* root) { TreeNode *curr = root; while (curr != nullptr) { if (curr->left != nullptr) { auto next = curr->left; auto predecessor = next; while (predecessor->right != nullptr) { predecessor = predecessor->right; } predecessor->right = curr->right; curr->left = nullptr; curr->right = next; } curr = curr->right; } } }; 作者:LeetCode-Solution 链接:https://leetcode-cn.com/problems/flatten-binary-tree-to-linked-list/solution/er-cha-shu-zhan-kai-wei-lian-biao-by-leetcode-solu/ 来源:力扣(LeetCode) 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
思路4:我的思路!!!巧!!!!先序遍历的反序
我的想法是:
用递归函数访问各个节点,递归调用的功能是,找到当前节点在链表中的下一节点,并把当前节点的右孩子指针指向它。
如何找?可能的情况有两种,如果当前节点存在左子树,那么目标节点是左子树被转换成链表后的尾节点;如果当前节点不存在左子树,那么目标节点就是右孩子节点(可能是NULL)。
所以在递归函数中可以这样实现:递归函数有两个参数,一个是当前节点,另一个是当前节点所在子树最后一个孩子在前序遍历下的下一个节点。返回值是当前节点所在子树的尾节点指向的节点。
递归函数的功能描述:
1.是否存在右子节点?若存在,访问右子节点同时传参:该右子树最右孩子的下一个节点;返回右子树的根节点作为当前节点的下一节点。
2.是否存在左子节点?若存在,访问左子节点同时传参:上一步得到的右子树的根节点(该左子树最右孩子的下一个节点);返回左子树的根节点作为当前节点的下一节点。
3.把当前节点指向上面得到的子节点,递归函数返回当前节点。
遍历顺序是先序的反序,也就是说,从根节点出发,会先找到右孩子,作为左子树在链表中的下一节点。在找左子树链表的尾节点时,不是通过先序遍历(因为这样破坏递归),而是通过先序遍历的反序,这样最先访问到的叶子就是子树在链表中的尾节点。这样在递归调用返回的时候,依次处理链表的连接即可,因为递归函数调用时的函数栈已经保留了链表的顺序。
我的实现
思路四:我的思路的实现
思路三:我的实现
/** * 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: TreeNode * search(TreeNode * root){ TreeNode * lastLeft = root; TreeNode * rightMem = root->right; if(root->left!=NULL){ lastLeft = search(root->left); lastLeft->left = NULL; lastLeft->right = root->right; root->right = root->left; root->left=NULL; } if(rightMem!=NULL) lastLeft = search(rightMem); cout<<root->val<<endl; return lastLeft; } void flatten(TreeNode* root) { if(root==NULL)return; search(root); //cout<<root->val<<"sd"<<endl; } };
拓展学习
树!!!