二叉树Part7

67 阅读7分钟

530.二叉搜索树的最小绝对差

题目链接

leetcode.cn/problems/mi…

思路

先中序遍历二叉树,得到一个数组,然后对数组进行处理,求其中元素的差值的绝对值的最小值

这样就可以把二叉树问题转换为数组问题

转化之后的数组是递增的,所以只需要算出相邻项之间的差值,然后去最小值即可

代码如下:

var getMinimumDifference = function(root) {
    let arr = [];
    const buildArr = node =>{
        if(node){
            buildArr(node.left);
            arr.push(node.val);
            buildArr(node.right);
        }
        return node;
    }
    buildArr(root);
    let res = Number.MAX_VALUE;
    for(let i = 0; i < arr.length; i++){
        res = res > arr[i+1]-arr[i] ? arr[i+1]-arr[i] : res;
    }
    return res;
};

501.二叉搜索树中的众数

题目链接

leetcode.cn/problems/fi…

思路

和上一题一样的,先转化为数组

再求一个递增数组中出现最多的元素

求出现最多的元素这一步,可以用哈希表

var findMode = function(root) {
     let arrays = [];
    const buildArr = node =>{
        if(node){
            buildArr(node.left);
            arrays.push(node.val);
            buildArr(node.right);
        }
        return node;
    }
    buildArr(root);
     let obj = {};
    let MaxNum = 0, MaxName = 0, res = [];
    for(let i=0;i<arrays.length;i++){
    let val = arrays[i];
    obj[val] === undefined? obj[val] = 1 : obj[val]++;
  }
    for(let key in obj){
        if(obj[key]> MaxNum){
            res = [];
            MaxNum = obj[key];
            res.push(key);
        }else if(obj[key] === MaxNum){
            
            res.push(key);
        }
    }
    return res;
    
};

这题改了很久,后面数组求出现最多的元素这一块已经不熟悉了

还有两个坑:

let MaxNum = 0, MaxName = 0, res = [];这里,最开始写的是let MaxNum, MaxName = 0, res = [];

没有赋初值,此时 MaxNum值为undefined,数字和undefined比较,总是返回false,因此,得不到答案

以及如果出现了好几个最大值,应该以数组形式返回

这两个点已经不熟了,需要好好回顾一下

实际上这里也可以用双指针法,对数组使用双指针法或者直接在二叉树中使用:

数组中使用:

    let maxLen = 0, res = [],j = 0;
    for(let i = 0; i < arrays.length; i++){
        let len = i - j + 1;
        if(arrays[i] === arrays[j]){
            if(len > maxLen){
                res = [];
                res.push(arrays[j]);
                maxLen = len;
            }else if(len === maxLen){
                res.push(arrays[j]);
        }
        }
        else{
            j = i;
            i--;
        }
        
    }
   
     return res;
};

数组中使用双指针没这么简单,需要考虑 1 2 的情况,所以,为了让,每次对新元素两个指针都在同一个位置,需要对i减一,以保证下一次i 和 j还是在同一个起点

这种方法有点类似于滑动窗口法,另一种简单一些的双指针法是这样的:

let pre = 0, cur = 0, maxLen = 0, count = 0;
     for( let cur = 0; cur < arrays.length; cur++){
         if(arrays[cur] == arrays[pre]){
             count++;
         }
         else{
             count = 1;
         }
         pre = cur;
         if(count > maxLen){
             res = [];
             res.push(arrays[cur]);
             maxLen = count;
         }else if(count == maxLen){
             res.push(arrays[cur]);
         }
     }
    return res;
};

每次pre指针都指向cur指针的前一位,然后用count来统计出现次数即可!

在树中使用,和上面双指针的原理差不多

var findMode = function(root) {
    // 不使用额外空间,使用中序遍历,设置出现最大次数初始值为1
    let count = 0,maxCount = 1;
    let pre = root,res = [];
    // 1.确定递归函数及函数参数
    const travelTree = function(cur) {
        // 2. 确定递归终止条件
        if(cur === null) {
            return ;
        }
        travelTree(cur.left);
        // 3. 单层递归逻辑
        if(pre.val === cur.val) {
            count++;
        }else {
            count = 1;
        }
        pre = cur;
        if(count === maxCount) {
            res.push(cur.val);
        }
        if(count > maxCount) {
            res = [];
            maxCount = count;
            res.push(cur.val);
        }
        travelTree(cur.right);
    }
    travelTree(root);
    return res;
};

这里把双指针的逻辑放到了单层递归逻辑中去

236. 二叉树的最近公共祖先

题目链接

leetcode.cn/problems/lo…

思路

用回溯,后序遍历就是天然的回溯

递归三部曲:

  • 确定递归函数的返回值和参数

    参数是root,左节点,右节点

    返回值:

    如果碰到p或者q,就把p,q返回了,否则,返回空

  • 确定终止条件

    遍历到null时,返回null,否则,返回p或者q

  • 确定单层递归逻辑

    left = 左边遍历结果

    right = 右边遍历结果

    单层递归逻辑处理

这里需要注意的是回溯的原理

image-20230702130730274

代码如下:

var lowestCommonAncestor = function(root, p, q) {
    // 确定参数和返回值,终止条件
    if(root == p || root == q || root == null) return root;
    // 确定单层递归逻辑
    let left = lowestCommonAncestor(root.left, p, q);
    let right = lowestCommonAncestor(root.right, p, q);
    // 注意单层递归逻辑中的返回值
    if(left !== null && right !== null) return root;
    else if(left === null) return right;
    else return left;
};

总结

这一题理顺还是非常难的,需要搞清楚回溯的逻辑和返回值的流向!

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

题目链接

leetcode.cn/problems/lo…

思路

这题是二叉搜索树,相比于二叉树,简单了许多

根据二叉搜索树的特性,只需要找到在[p,q]这个公共区间的节点即可

递归三部曲:

  • 确定参数和返回值

    这里参数是root,p,q

    返回值是满足条件的节点

  • 确定终止条件

    遇到null就返回

  • 确定单层递归逻辑

    如果节点在该区间,则返回该节点

    否则,root.val < p,则返回节点右子树

    root.val > q, 则返回节点左子树

代码如下:

var lowestCommonAncestor = function(root, p, q) {
    // 确定终止条件
    if(root === null) return root;
    // 单层递归逻辑
    if(root.val < p.val && root.val < q.val){
        let right = lowestCommonAncestor(root.right,p,q);
        // 在右子树找到了该值
        if(right){
            return right;
        }
    }
    if(root.val > p.val && root.val > q.val){
        let left = lowestCommonAncestor(root.left,p,q);
        // 在左子树找到了该值
        if(left){
            return left;
        }
    }
    return root;
};

701.二叉搜索树中的插入操作

题目链接

leetcode.cn/problems/in…

思路

看上去有点难,实际上就是先找到可以插入的节点位置,然后把原本是null的节点替换为待插入的节点

递归三部曲:

  • 确定参数和返回值

    参数就是root,返回值为当前节点

  • 确定终止条件

    终止条件就是找到遍历的节点为null的时候,就是要插入节点的位置了,并把插入的节点返回。

  • 确定单层递归逻辑

    如果root.val > target,若左子树为null,则target为节点左节点值,否则,遍历root的左子树

    root.val < target 同理

var insertIntoBST = function(root, val) {
    if(root === null){
        const node = new TreeNode(val);
        return node;
    }
    if(root.val < val) root.right = insertIntoBST(root.right, val); // 我的右子树是新建的节点
    else if(root.val > val) root.left =  insertIntoBST(root.left, val);
    return root;
};

有返回值的实际上是简化过后的做法,一般的想法是找到可以插入的节点的位置,然后插入,这样的话无需返回值

而没有返回值的做法相当于找到改位置,然后创建节点,再把节点返回

没有返回值的做法复杂一些:

var insertIntoBST = function (root, val) {
    // 创建一个节点,作为找到null节点时,该null节点的父节点
    let parent = new TreeNode(0);
    const preOrder = (cur, val) => {
        if (cur === null) {
            let node = new TreeNode(val);
            if (parent.val > val)
                parent.left = node;
            else
                parent.right = node;
            return;
        }
        // 在这里对parent赋值
        parent = cur;
        if (cur.val > val)
            preOrder(cur.left, val);
        if (cur.val < val)
            preOrder(cur.right, val);
    }
    // 因为前面有parent= cur,所以需要这一条
    if (root === null)
        root = new TreeNode(val);
    preOrder(root, val);
    return root;
};

没有返回值的做法是一般直观的想法,但是相对来说复杂不少,所以可以用有返回值的情况,通过返回一个节点来减少逻辑

450.删除二叉搜索树中的节点

题目链接

leetcode.cn/problems/de…

思路

删除就涉及到树的结构的调整了,有点类似于插入操作的无返回值的情况

递归三部曲:

  • 确定参数和返回值

    参数就是root和待删除节点

    返回值是节点

  • 确定终止条件

    遇到null返回

  • 确定单层递归逻辑

    这里分五类

    • 没找到删除节点,遍历空直接返回
    • 节点没有子节点,直接删除
    • 左孩子为空右不为空,删除节点,右孩子补位,此时返回右孩子
    • 右孩子为空左不为空,删除节点,左孩子补位,此时返回左孩子
    • 左右都不为空,把删除节点的左子树头结点(左孩子)放到删除节点的右子树的最左面节点的左孩子上,返回删除节点右孩子为新的根节点

    代码如下:

    var deleteNode = function(root, key) {
        if(!root) return null;
        if(key > root.val){
            // 如果key大于root的值,则改右边
            root.right = deleteNode(root.right, key);
            return root;
        }else if(key < root.val){
            root.left = deleteNode(root.left, key);
            return root;
        }else{
            // 找到了key对应的节点,需要分情况对其进行处理
            // 情况二,节点没有子节点,直接删除
            if(!root.left && !root.right) return null;
            // 情况三,节点只有左节点
            else if(root.left && !root.right) return root.left;
            // 情况四,节点只有右子节点
            else if(!root.left && root.right) return root.right;
            // 情况五,节点左右孩子都有
            else{
                // 获取右子树最小的节点
                const minNode = getMinNode(root.right);
                // 把左子树变成右子树最小的节点
                minNode.left = root.left;
                return root.right;
            }
        };
        
        function getMinNode(root) {
        while (root.left) {
            root = root.left;
        }
        return root;
        }
    };
    

这题的重点在于把删除节点变成了return 节点

即,要删除该节点,则直接返回删除节点之后节点的情况即可

669. 修剪二叉搜索树

题目链接

leetcode.cn/problems/tr…

思路

递归三部曲:

  • 确定参数和返回值

    参数就是root和区间

    返回值是节点

  • 确定终止条件

    遇到null返回

  • 确定单层递归逻辑

    如果当前节点值小于low的值,则递归右子树

    当前节点值大于high的值,递归左子树

    接下来要将下一层处理完左子树的结果赋给root->left,处理完右子树的结果赋给root->right

var trimBST = function(root, low, high) {
    if(!root) return null;
    // 对不满足情况的节点进行操作,返回其左/右满足情况的节点
    if(root.val < low){
        let right = trimBST(root.right, low, high)
        return right;
    }
    if(root.val > high){
       let left = trimBST(root.left, low, high)
        return left;
    }
    // 将其接住
    root.left = trimBST(root.left, low, high);
    root.right = trimBST(root.right, low, high);
    return root;
};

这一题看上去简单,但是需要好好理解,可以画图来进行辅助