进阶刷题-DFS

个人觉得路径总和系列问题是DFS中比较经典的提醒,整合到了一篇文章,感兴趣可以看一下:传送门
二叉树的中序遍历可以说是DFS的基础问题,但是其三种解法难度递增,个人觉得对于理解DFS的本质有一定的帮助,笔者将其三种解法整合到了一篇文章:传送门

LeetCode100-相同的树Easy
经典DFS编程,两个树同步进行就好了~
进阶刷题-DFS_第1张图片

class Solution {
public:
    bool isSameTree(TreeNode* p, TreeNode* q) {
        if(p==nullptr&&q==nullptr) return true;
        if(!(p!=nullptr&&q!=nullptr)) return false;
        return p->val==q->val&&isSameTree(p->left,q->left)&&isSameTree(p->right,q->right);
    }
};

LeetCode98-验证二叉搜索树Middle
解法一:根据二叉搜索树的特性可知,其中序遍历结果为一个递增数组,我们可以中序遍历二叉树,同时检查新保存的数据是否满足递增。
(注意在便利的同时就可以进行判断,这样节省掉额外遍历结果数组的时间)

class Solution
{
public:
    bool isValidBST(TreeNode *root)
    {
        vector<int> nums;
        stack<TreeNode* > stk;
        while(root!=nullptr||!stk.empty())
        {
            while(root!=nullptr)
            {
                stk.push(root);
                root=root->left;
            }
            root=stk.top();
            if(nums.size()&&root->val<=nums.back()) return false;//遍历同时检查数组
            nums.push_back(root->val);
            root=root->right;
            stk.pop();
        }
        return true;
    }
    
};

解法二:DFS每个节点,判断当前节点是否满足二叉搜索树的条件,对于每一个节点来说,他都要满足在一个上下限内,这里要考虑到子节点不单单要考虑直接的父节点,还要考虑更上层的根节点。
使用两个变量用于限定范围。
(最开始考虑DFS编程时,思路是对于每个节点我去看它的左右子节点,但是这样会出现针对左右子树我们要采取不同的策略,可以完成题目要求,但是编程过于冗余)

class Solution
{
public:
    bool isValidBST(TreeNode *root)
    {
        return DFS(root, -__LONG_MAX__, __LONG_MAX__);
    }
    bool DFS(TreeNode *root, long upper, long lower)
    {
        if (root == nullptr)
            return true;
        return root->val < lower && root->val > upper && DFS(root->left, upper, root->val) && DFS(root->right, root->val, lower);
    }
};

LeetCode110-平衡二叉树Middle
解法一:自顶向下-常规DFS思路
递归的访问每个子树,同时使用递归的方法求出其左右子树高度,然后进行判断;即递归嵌套递归使用。
时间复杂度O(n^2)(对于高度存在了很多的重复计算)
空间复杂度O(n)

class Solution
{
public:
    bool isBalanced(TreeNode *root)
    {
        if (root == nullptr)
            return true;
        return abs(dfs(root->left) - dfs(root->right)) <= 1 && isBalanced(root->left) && isBalanced(root->right);
        //递归的对每个节点判断左右子树高度差是否满足条件
    }
    int dfs(TreeNode *root) //求每个节点的高度
    {
        if (root == nullptr)
            return 0;
        return max(dfs(root->left), dfs(root->right)) + 1;
    }
};

解法二:自底向上的递归(类比于后序遍历的思路)
递归的求每个节点左右子树的高度,同时判断,在返回值中体现其是否满足要求。
时间复杂度O(n)
空间复杂度O(n)

class Solution
{
public:
    bool isBalanced(TreeNode *root)
    {
        if (dfs(root) == -1)
            return false;
        return true;
    }
    int dfs(TreeNode *root)
    {
        if (root == nullptr)
            return 0;
        int leftHeight;
        int rightHeight;
        if ((leftHeight = dfs(root->left)) == -1 || (rightHeight = dfs(root->right)) == -1 || abs(leftHeight - rightHeight) > 1)
        //在判断过程中赋值可以在左子树不满足时不需要计算对应右子树,节省一点时间
        {
            return -1; //-1代表当前子树不符合要求
        }
        return max(leftHeight, rightHeight) + 1; //返回符合要求子树的高度
    }
};

LeetCode99-恢复二叉搜索树Middle
两种解法共通点:我们通过中序遍历结果是否保持递增找出被交换的数字所在位置,每次找到一对递减的数对,要考虑到交换节点的结果是产生一对或两对数对,第一对数字一定包含第一个应该被交换的节点,但寻找第二个节点要通过继续向后遍历看是否有第二对数对。
解法一:利用二叉搜索树中序遍历有序递增的特性,遍历整个树,然后遍历保存路径的数组中找到应该被交换的两个点。
时空复杂度均为O(N)

class Solution
{
public:
    void recoverTree(TreeNode *root)
    {
        //利用二叉搜索树的中序遍历有序特性
        vector<TreeNode *> nums;
        DFS(root, nums);
        TreeNode *p = nullptr, *q = nullptr;
        for (int i = 0; i < nums.size() - 1; i++)
        {
            //交换两个节点,在中序遍历中产生一对或两对递减数对
            //当只有一对时说明交换的时相邻节点
            //两对对应交换的节点之间存在一定的间隔
            //我们记录第一对数对,如果存在第二对,那就更新第二个点,反之直接输出
            if (nums[i]->val >= nums[i + 1]->val)
            {
                if (p == nullptr)
                    p = nums[i];
            }

            if (nums[i]->val >= nums[i + 1]->val)
            {

                q = nums[i + 1];
            }
        }
        if (q == nullptr)
            q = p + 1;
        int temp = p->val;
        p->val = q->val;
        q->val = temp;
    }
    void DFS(TreeNode *root, vector<TreeNode *> &nums)
    {
        if (root == nullptr)
            return;
        DFS(root->left, nums);
        nums.push_back(root);
        DFS(root->right, nums);
    }
};

解法二:Morris中序遍历
关于Morris算法具体思路与实现可见笔者另一篇文章-传送门
本题中只需要根据找交换点的特性更改一部分代码即可。
时间复杂度O(N)(实际上是2N)
空间复杂度O(1)

class Solution
{
public:
    void recoverTree(TreeNode *root)
    {
        TreeNode *PreTreeNode = nullptr, *a = nullptr, *b = nullptr;
        // a,b分别用于保存两个要交换的节点,pre用于保存当前节点前一个节点,用于比较
        TreeNode *predecessor = nullptr;
        while (root != nullptr)
        {
            if (root->left != nullptr)
            {
                // predecessor 节点就是当前 root 节点向左走一步,然后一直向右走至无法走为止
                predecessor = root->left;
                while (predecessor->right != nullptr && predecessor->right != root)
                //找到左子树中最右的节点,用predecessor标记
                {
                    predecessor = predecessor->right;
                }
                if (predecessor->right == nullptr)
                //说明当前是用右指针建立父子连接的过程
                // 让 predecessor 的右指针指向 root,继续遍历左子树
                {
                    predecessor->right = root;
                    root = root->left;
                }
                else
                //当前已建立好连接,处于获取val值求res的过程
                // 说明左子树已经访问完了,我们需要断开链接
                {
                    //直接在遍历的程序上将每次记录改为判断是否符合二叉搜索树规律
                    if (PreTreeNode != nullptr)
                    {
                        if (root->val <= PreTreeNode->val)
                        {
                            if (a == nullptr)
                                a = PreTreeNode;
                            b = root;
                        }
                        else
                        {
                            PreTreeNode = root;
                        }
                    }
                    else
                        PreTreeNode = root;
                    predecessor->right = nullptr;
                    //为了恢复树的结构,如果只是为了求便利的结果实际上这一步可有可无,不影响遍历的结果
                    // leetcode测试中注释掉这行代码会报错,猜测可能是因为更改了树的结构
                    //而测试用例是通过恢复这一树结构重新填值来改变的
                    root = root->right;
                }
            }
            // 如果没有左孩子,则直接访问右孩子
            else
            {
                if (PreTreeNode != nullptr)
                {
                    if (root->val <= PreTreeNode->val)
                    {
                        if (a == nullptr)
                            a = PreTreeNode;
                        b = root;
                    }
                    else
                    {
                        PreTreeNode = root;
                    }
                }
                else
                    PreTreeNode = root;

                root = root->right;
            }
        }
        swap(a->val, b->val);
    }
};

DFS一般采用递归的形式,使用栈结构先进后出的特性,核心在于状态回溯。
DFS本身的思路其实很简单,只要一直关注“深度优先”四个字进行思考很容易都可以得到一种编程解法,但是很多时候时空消耗都不尽人意,要与其他的方法进行适当的结合…
大概就先写这么多,最近忙着别的事情每天就只是靠每日一题养老有些懈怠,后面会选择新的模块继续大量刷题的!

你可能感兴趣的:(leetcode,算法,dfs)