Leetcode99 恢复二叉搜索树

空扰寡人 提交于 2019-12-04 02:31:43

写在题目前的话

第一次写这个博客以后我发现我理解错了题目,但是我的问题更具有一般性,更复杂,所以文章就不改了

题目:只有两个结点被错误的交换。

我的:有任意多个结点被错误的交换。

题目:


先分析题目:使用O(n)空间复杂度的解法很容易实现,那么我们先看看很容易实现是怎实现的。

算法1:

1、中序遍历二叉树,并将中序序列保存在一个数组Numbers中

2、对Numbesr进行排序

3、中序遍历二叉树,并用Numbers序列按遍历顺序覆盖掉二叉树中的每一个值

此算法即是我想到的空间复杂度为O(n)容易的解法

123的时间复杂度分别为O(N), O(NlogN), O(N),因此上述算法时间复杂度为O(NlogN)


接下来,看一下使用常数空间怎么解决这个问题。

先注意一点

void recoverTree(TreeNode* root)

通过题目给的接口函数可以看出,是值传递。因此通过对原树进行遍历并重新构建一个新树,再把新树根结点赋值给root的方法是不成立的,而且题目说明,树的结构不发生改变,也能够理解到,这个题目是要直接交换树结点的值。

根据之前算法1的启发,其实要做的就是对二叉树进行排序,使得其中序遍历序列为有序序列即可。而中序遍历能够得到什么?

能得到一个按次序访问的结点序列,如果每次访问下一个结点之前,都记录一个之前访问的结点,那么就可以得到一对相邻结点。

然而思考的顺序有些颠倒,设置一个之前访问的结点并不是突发奇想,而在于对排序算法知识的积累。题目核心在于排序,因此需要思考排序算法与此题目的关联性,哪些排序算法能够比较容易套进来?常见的有选择排序,冒泡排序,插入排序,快速排序。快速排序不考虑因为比较复杂,不论是选轴值和从两端遍历都不是那么好实现。选择排序每次需要从第K个位置去更新,也不太舒服。因此冒泡和插入排序是值得考虑的点。虽然是在用排除法,其实最开始就想到了冒泡排序,只是在总结时候强行加了这么一个思考方式,因为冒泡排序是比较相邻元素,中序遍历也比较容易得到一个相邻序列。

算法2:

每次比较相邻的访问结点,并将数值较大的结点往后冒泡,最终就能得到有序的序列,在这个题目中,需要一个改进的冒泡排序,当任何一次冒泡过程没有发生元素交换,则冒泡过程结束,排序完成,否则就需要先统计树的结点数目n,然后再进行n次冒泡,多次一举。

具体算法可以结合代码理解:

class Solution {
public:
    void recoverTree(TreeNode* root) {
        function<bool(TreeNode*)> sortTree;
        TreeNode* pre = NULL;
        sortTree = [&sortTree, &pre](TreeNode* root)->bool{
            if (!root) { return true; }
            bool ok = true;
            ok = ok && sortTree(root->left);
            if (pre != NULL && root->val < pre->val) {
                swap(root->val, pre->val);
                ok = false;
            }
            pre = root;
            ok = ok && sortTree(root->right);
            return ok;
        };
        do {
            pre = NULL;
        }
        while (!sortTree(root));
    }
};

这里用到了C++的lambda表达式,因为匿名参数的闭包特点,能够将算法封闭在函数内部,所以很优雅。效率仍未考知。

总结下这个题目的知识点:

1、二叉排序树的定义。左子树结点都小于根结点的值,右子树的结点都大于根结点的值,子树也是二叉排序树。

2、二叉排序树可以通过中序遍历得到有序序列,中序序列的逆序列可以得到降序序列。

3、基础排序算法的流程,特点。

4、递归函数的技巧,中序遍历二叉搜索树过程中,得到序列需要一个递归函数外的变量来保持索引过程。因此递归函数并不是完全封闭的。很多递归函数都用到了类似的技巧,通过访问外部全局或成员函数来获得快捷访问,也避免了递归函数参数传递过多的问题,但从封装的角度上来说,这种做法是并不优雅的。

5、*lambda(非必要)

额外的话

虽然通过算法二解决了这个挑战,也能成功AC,但冒泡排序的时间复杂度为O(N^2),并不是非常优秀,通过算法一可以看到,这个题目的时间复杂度是可以达到理论最优的O(N*logN)的,因此该算法也只能说解决了问题,而没有较完美的解决问题,如果空间复杂度能控制在O(1),时间复杂度控制在O(N*logN),那么应该是对这个题目一个完美答卷了。感兴趣的同学可以自己思考思考,是否能做的更好。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!