[日记]LeetCode算法·六——二叉树③ 二叉搜索树

1 二叉搜索树的搜索

LeetCode:二叉搜索树的搜索

①遍历法

因为二叉搜索树的特性,可以采用简单的循环进行搜索,小则左,大则右。
这个简单的题目是以后很多算法题的根基,对二叉搜索树的定义与基本特性的考察。

class Solution {
public:
    TreeNode* searchBST(TreeNode* root, int val) {
        TreeNode* cur_node=root;
        while(true)
        {
            if(cur_node->val==val)return cur_node;
            //在左子树
            else if(cur_node->val>val)
            {
                if(cur_node->left==nullptr)return cur_node->left;
                else cur_node=cur_node->left;
            }
            else
            {
                if(cur_node->right==nullptr)return cur_node->right;
                else cur_node=cur_node->right;
            }
        }
    }
};

2 验证二叉搜索树

LeetCode:验证二叉搜索树

①定义法

利用二叉搜索树的特性,即根节点值大于左子树最大值,小于右子树最小值(不能使用大于左子节点+小于右子节点判断)

class Solution {
public:
    class MVal
    {
        public:
            int max_val;
            int min_val;
            bool flag;

            MVal(int maxval,int minval,bool f)
            {
                max_val=maxval;
                min_val=minval;
                flag=f;
            }
    };

    MVal isValidBSTAssist(TreeNode* root)
    {
        if(root->left==nullptr&&root->right==nullptr)
            return MVal(root->val,root->val,true);
        
        if(root->left&&root->right==nullptr)
        {
            MVal left=isValidBSTAssist(root->left);
            if(left.flag && left.max_val<root->val)
                left.flag=true;
            else
                left.flag=false;

            left.max_val=max(left.max_val,root->val);
            return left;
        }

        if(root->right&&root->left==nullptr)
        {
            MVal right=isValidBSTAssist(root->right);
            if(right.flag && right.min_val>root->val)
                right.flag=true;
            else
                right.flag=false;

            right.min_val=min(right.min_val,root->val);
            return right;
        }

        MVal left=isValidBSTAssist(root->left);
        MVal right=isValidBSTAssist(root->right);
        bool f1;
        if(right.flag && right.min_val>root->val && left.flag && left.max_val<root->val)
            f1=true;
        else
            f1=false;
        
        int Max_Val=max(root->val,right.max_val);
        int Min_Val=min(root->val,left.min_val);
        return MVal(Max_Val,Min_Val,f1);
    }

    bool isValidBST(TreeNode* root) {
        MVal result=isValidBSTAssist(root);
        return result.flag;
    }
};

②中序遍历递增法

事实上,二叉搜索树具有着较强的顺序规律,其中序遍历结果一定为递增序列,因此可以通过中序遍历结果进行判断。

class Solution {
public:
    long max_val=-2147483649;
    bool isValidBST(TreeNode* root) {
        bool left=true;
        bool right=true;
        //左
        if(root->left)left=isValidBST(root->left);

        //中
        if(root->val<=max_val)return false;
        else
        {
            max_val=root->val;
        }
        //右
        if(root->right)right=isValidBST(root->right);

        return left&&right;
    }
};

3 二叉搜索树的最小绝对差

LeetCode:二叉搜索树的最小绝对差

①定义法

从最基础的思路出发,因为left.left < left.right< root < right.left< right,所以结果应该在左子树的最右边或右子树的最左边与root的差,借助这个思路可以递归求解。

class Solution {
public:
    int getMinimumDifference(TreeNode* root) {
        //因为left.left<left.right<root<right.left<right
        //所以结果应该在左子树的最右边或右子树的最左边与root的差

        int val,res,res_left,res_right;
        TreeNode* cur_node;

        //递归终止,叶节点
        if(root->left==nullptr&&root->right==nullptr)return -1;

        //只有左子树
        else if(root->left&&root->right==nullptr)
        {
            cur_node=root->left;
            while(cur_node)
            {
                val=cur_node->val;
                cur_node=cur_node->right;
            }
            res=abs(root->val-val);
            res_left=getMinimumDifference(root->left);
            if(res_left==-1)return res;
            else return min(res,res_left);
        }
        //只有右子树
        else if(root->left==nullptr&&root->right)
        {
            cur_node=root->right;
            while(cur_node)
            {
                val=cur_node->val;
                cur_node=cur_node->left;
            }
            res=abs(root->val-val);
            res_right=getMinimumDifference(root->right);
            if(res_right==-1)return res;
            else return min(res,res_right);
        }
        //左右子树都在
        else
        {
            //左
            cur_node=root->left;
            while(cur_node)
            {
                val=cur_node->val;
                cur_node=cur_node->right;
            }
            res=abs(root->val-val); 
            //右
            cur_node=root->right;
            while(cur_node)
            {
                val=cur_node->val;
                cur_node=cur_node->left;
            }
            res=min(res,abs(root->val-val));

            res_left=getMinimumDifference(root->left);
            res_right=getMinimumDifference(root->right);
            if(res_left==-1)res_left=res+1;
            if(res_right==-1)res_right=res+1;
            return min(res,min(res_left,res_right));
        }
    }
};

②中序遍历递增法

中序遍历递增,直接比较相邻元素即可,使用迭代法是最简单明了的,这里采用了递归算法,因此需要外置全局变量minDiff与pre。

class Solution {
public:
    TreeNode* pre_node=nullptr;
    int minAbsDiff=-1;
    int getMinimumDifference(TreeNode* root) {
        //中序遍历,单调递增

        //终止条件
        if(root==nullptr)return 0;
        //左
        getMinimumDifference(root->left);
        //中
        if(pre_node==nullptr)
        {   
            pre_node=root; 
        }
        else
        {
            if(minAbsDiff==-1)
            {
                minAbsDiff=abs(root->val-pre_node->val);
            }
            else
            {
                minAbsDiff=min(minAbsDiff,abs(root->val-pre_node->val));
            }
            pre_node=root;
        }
        //右
        getMinimumDifference(root->right);
        return minAbsDiff;
    }
};

4 二叉搜索树中的众数

LeetCode:二叉搜索树中的众数

依然是套路题,中序遍历解决。

class Solution {
public:
    int maxCount=0;
    int count=0;
    TreeNode* pre=nullptr;
    vector<int> result;
    //利用BST的中序遍历性质
    //相等的数在中序遍历中会一致
    void findModeAssist_search(TreeNode* root)
    {
        //递归终止
        if(root==nullptr)return;
        //左
        findModeAssist_search(root->left);

        //中
        if(pre==nullptr)
        {
            count=1;
        }
        else if(pre->val==root->val)
        {
            count++;
        }
        else
        {
            count=1;
        }

        //更新节点
        pre=root;

        if(count==maxCount)result.push_back(root->val);

        //右
        if(count>maxCount)
        {
            result.clear();
            maxCount=count;
            result.push_back(root->val);
        }

        findModeAssist_search(root->right);
    }
    vector<int> findMode(TreeNode* root) {
        findModeAssist_search(root);
        return result;
    }
};

5 二叉树的最近公共祖先

LeetCode:二叉树的最近公共祖先

由于是普通二叉树,没有二叉搜索树的高度序列性,因此必须遍历整棵树才能获得最小公共祖先。
本道题可以使用后序遍历的思想进行处理,即左右子树的结果作为根节点的处理逻辑的基石。
一般而言,对于递归后的子树,返回的必定是节点、节点的最小祖先节点与空节点三种情况。
那么对于根节点而言,如果左右子树均非空节点,意味着自己就是最小的祖先节点,因为在自己之后,所有递归节点必然有至少一边是空节点。
如果根节点的左右子树只有一边是非空,那么存在两种情况:一为祖先节点依然在根节点之上,那么此时应该返回节点,而不是自身;二为祖先节点已经被找到,传来的非空节点即为结果,那么我们依然必须传递非空节点,而不能更改。
如果左右两边都为空节点,那自然是返回空节点作为路径错误的信号。
总而言之,本道题中,需要记住本道题中所返回的节点必然是空节点、答案、节点三种情况,在根节点并非答案的情况下,不能修改返回值导致判断错误。

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        //返回当前节点
        if(root->val==p->val||root->val==q->val)return root;

        TreeNode* left=nullptr;
        TreeNode* right=nullptr;
        if(root->left)left=lowestCommonAncestor(root->left,p,q);
        if(root->right)right=lowestCommonAncestor(root->right,p,q);

        //左右子树都非空,说明目前是最近公共祖先,且之后的回溯节点都是一边空一边非空
        if(left&&right)return root;

        //此时有两种可能,一旦成立就意味着right=nullptr
        //1 正在寻找祖先的路上,left代表的只是非空
        //2 已找到祖先,将结果返回的路上。left代表的是最终结果
        if(left)return left;

        //理论上应该与left一致,right不成立时的时候返回nullptr
        //此处为省略写法,因为现在只有left=nullptr的情况了
        //那么right代表三种可能,
        //1 正确的子树在寻找祖先的路上,代表非空
        //2 同left2,代表结果
        //3 错误的子树,代表空
        return right;
    }
};

6 二叉搜索树的最近公共祖先

LeetCode:二叉搜索树的最近公共祖先

因为是二叉搜索树,所以直接利用其有序性进行判断即可。最近祖先节点要么是两个节点之一,那么是节点值介于两个节点之中。

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        TreeNode* cur_node=root;
        //祖先
        if(cur_node->val==p->val||cur_node->val==q->val)return cur_node;
        if((p->val<cur_node->val)&&(q->val>cur_node->val))return cur_node;
        if((p->val>cur_node->val)&&(q->val<cur_node->val))return cur_node;

        //同方向,更改cur_node
        if(p->val>cur_node->val&&q->val>cur_node->val)return lowestCommonAncestor(cur_node->right,p,q);
        else return lowestCommonAncestor(cur_node->left,p,q);
    }
};

7 二叉搜索树中的插入操作

LeetCode:二叉搜索树中的插入操作

二叉树的插入操作,必须注意维持二叉树的自身序列特征。利用定义一路查询到空节点(插入位置),并利用第二个指针记录其父节点,进行插入即可。

class Solution {
public:
    TreeNode* insertIntoBST(TreeNode* root, int val) {
        //特殊情况处理
        if(root==nullptr)
        {
            root=new TreeNode(val);
            return root;
        }

        TreeNode* parent=root;
        TreeNode* cur_node=root;
        while(cur_node)
        {
            parent=cur_node;
            if(cur_node->val>val)cur_node=cur_node->left;
            else cur_node=cur_node->right;
        }

        if(parent->val>val)parent->left=new TreeNode(val);
        else parent->right=new TreeNode(val);

        return root;
    }
};

8 删除二叉搜索树中的节点

LeetCode:删除二叉搜索树中的节点

相比于插入节点的轻便,删除节点意味着对二叉搜索树的结构调整,需要分情况讨论。
1 删除叶节点是最简单的,直接断开删除节点与父节点连接即可。
2 对于单边节点也是相对容易的,只需要将子节点与父节点进行连接即可。
3 最为复杂的是双边节点,我们需要寻找一个节点替代删除节点的位置,以维持二叉搜索树的结构。一般而言有两个思路,由于max(left)< root< min(right),因此我们可以用左子树最大值 / 右子树最小值代替删除节点,而左子树最大值与右子树最小值必然是单边节点或叶节点,删除就容易多了。
这里采用右子树最小值。

class Solution {
public:
    int findMin(TreeNode* root)
    {
        TreeNode* cur_node=root;
        while(cur_node->left)
        {
            cur_node=cur_node->left;
        }
        return cur_node->val;
    }
    TreeNode* deleteNode(TreeNode* root, int key) {
        //递归终止条件
        if(root==nullptr)return nullptr;
        if(key==root->val)
        {
            //叶节点
            if(root->left==nullptr&&root->right==nullptr)
            {
                return nullptr;
            }
            //左子树
            else if(root->left&&root->right==nullptr)
            {
                TreeNode* result=root->left;
                return result;
            }
            //右子树
            else if(root->right&&root->left==nullptr)
            {
                TreeNode* result=root->right;
                return result;
            }
            //左右子树都在
            else
            {
                int right_min=findMin(root->right);
                root->val=right_min;
                root->right=deleteNode(root->right,right_min);
                return root;
            }
        }
        else if(key>root->val)root->right=deleteNode(root->right,key);
        else root->left=deleteNode(root->left,key);
        return root;
    }
};

9 修剪二叉搜索树

LeetCode:修剪二叉搜索树

有两种思路思考这道题。
1 思考根节点是否需要删除。需要删除根节点时,由于二叉搜索树的有序性,必然有一边的子树也会被完全删除,因此直接返回另一边的递归结果即可;而不需要删除时,则是将根节点与左右子树递归结果进行连接。
2 利用二叉搜索树的有序性,寻找需要保留的区间。即根节点需要删除时,利用有序性,去修剪其中一边的子树,在递归的帮助下会自然地寻找到合适的区间。

class Solution {
public:
    TreeNode* trimBST(TreeNode* root, int low, int high) {
        //叶节点可以直接删除返回nullptr
        //如果root需要删除的情况下,则root左右子树的返回结果至多只有一个不为nullptr
        //因为一旦两边都不为nullptr,意味着low<left_return<root<right-return<high,那么root不应该删除
        //因此,一旦root需要删除,只要返回左右子树修剪结果中不为nullptr的即可
        if(root==nullptr)return nullptr;

        //根节点需要删除的情况下
        if(root->val<low||root->val>high)
        {
            TreeNode* left=trimBST(root->left,low,high);
            TreeNode* right=trimBST(root->right,low,high);

            if(left)return left;
            else return right;
        }
        //根节点不需要修剪的情况下
        //修剪左右子树即可
        root->left=trimBST(root->left,low,high);
        root->right=trimBST(root->right,low,high);
        return root;
    }
};

10 将有序数组转换为二叉搜索树

LeetCode:将有序数组转换为二叉搜索树

相比于中序遍历+后序遍历构造二叉树,在思考逻辑上二者完全一致,但本题有更加简单一些。
利用中序数组递增的特性,不断地二分数组,构造二叉树节点并连接即可。

class Solution {
public:
    TreeNode* sortedArrayToBSTAssist(vector<int>& nums,int start,int end)
    {
        if(end<start)return nullptr;

        int val_index=(start+end)/2;
        TreeNode* root=new TreeNode(nums[val_index]);

        if(end==start)return root;

        root->left=sortedArrayToBSTAssist(nums,start,val_index-1);
        root->right=sortedArrayToBSTAssist(nums,val_index+1,end);
        return root;
    }
    TreeNode* sortedArrayToBST(vector<int>& nums) {
        return sortedArrayToBSTAssist(nums,0,nums.size()-1);
    }
};

11 把二叉搜索树转换为累加树

LeetCode:把二叉搜索树转换为累加树

思考到累加树的顺序是从最大节点累加到最小节点,而二叉搜索树的中序遍历是升序序列,那么自然地,我们反向地进行中序遍历。每次的根节点赋值为右节点(累加结果)+根节点值,不断递归即可完成累加树的构造。
对sum=0的初始化,可以有效地对最大值的累加进行异常情况排除。

class Solution {
public:
    int sum=0;
    TreeNode* convertBST(TreeNode* root) {
        //异常处理,也是递归终止条件
        if(root==nullptr)return root;
        //中序遍历思想
        //但需要注意到,实际上的顺序为右-中-左,而非左-中-右

        //右
        convertBST(root->right);

        //中
        sum+=root->val;
        root->val=sum;

        //左
        convertBST(root->left);

        return root;
    }
};

12 总结

老本啃完喽,同时也开学了,接下来要好好学习回溯、动态规划、双指针的内容。
至于树这一方面,还有很多AVL、B树、B+树、最小顶堆、红黑树等内容需要进一步深化学习,只能说慢慢来了。
另外GAN虽然不怎么吃设备要求,但DCGAN太tm难训练了,以前听过别人的强调,但自己亲自上手才发现这玩意的难度之大。生成器G和判别器D很难达到旗鼓相当的地步,不得不说判别这一个任务确实比生成要简单多了。
虽然在理论上还有着JK损失的问题啊,不如WGAN的损失设计之类,以及图片的数据分布的狭窄之类的,明天打算上传自己DCGAN的代码到github上,慢慢开始学习github管理自己的代码和实验成果。
吐槽一下:开学的前几门课都有点折磨,感觉很难学到自己想学的东西,本来打算白天刷题,晚上上课写这个MarkDown的,结果老师不让用电脑,真是绝了,下一次一定打印一些算法或论文过去看。

——2023.2.20

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值