Day17-18-二叉树-leetcode654,617,700,98 - 530,501,236

  1. 最大二叉树
  • 给定一个不重复的整数数组 nums 。 最大二叉树 可以用下面的算法从 nums 递归地构建:

  • 创建一个根节点,其值为 nums 中的最大值。

  • 递归地在最大值 左边 的 子数组前缀上 构建左子树。

  • 递归地在最大值 右边 的 子数组后缀上 构建右子树。

  • 返回 nums 构建的 最大二叉树 。

  • 提示:

  • 1 <= nums.length <= 1000

  • 0 <= nums[i] <= 1000

  • nums 中的所有整数 互不相同

  • 思路

  • 构造树,一般采用的是前序遍历,因为先构造中间节点,然后递归构造左子树和右子树。

    1. 确定递归函数的参数和返回值,参数=》存放元素的数组,返回=》该数组构造的二叉树的头节点
    1. 确定终止条件,提示1 <= nums.length <= 1000,即输入数组大小大于1,则不需要考虑小于1的情况,当递归遍历时,若传入的数组大小为1,说明遍历到了叶子节点了。那么应该定义一个新的节点,并把这个数组的数值赋给新的节点,然后返回这个节点。 这表示一个数组大小是1的时候,构造了一个新的节点,并返回。
    1. 确定单层递归的逻辑:(1)先找到数组中最大的值和对应的下标,最大的值构造根节点,下标用来分割数组;(2)最大值所在的下标左区间构造左子树;(3)最大值所在的下标右区间,构造右子树;
  • 用数组构造二叉树的题目,每次分隔尽量不要定义新的数组,而是通过下标索引直接在原数组上操作,这样可以节约时间和空间上的开销。

var constructMaximumBinaryTree = function (nums) {
    // 递归函数,参数为数组arr、左右边界left和right
    const BuildTree = (arr, left, right) => {
        // 终止条件:如果左边界大于右边界,说明区间为空,返回null
        if (left > right)
            return null;
        // 找到[left, right]区间内的最大值和下标
        let maxValue = -1;
        let maxIndex = -1;
        for (let i = left; i <= right; ++i) {
            if (arr[i] > maxValue) {
                maxValue = arr[i];
                maxIndex = i;
            }
        }
        // 用最大值创建当前根节点
        let root = new TreeNode(maxValue);
         // 递归构建左子树(最大值左边的区间)
        root.left = BuildTree(arr, left, maxIndex - 1);
          // 递归构建右子树(最大值右边的区间)
        root.right = BuildTree(arr, maxIndex + 1, right);
        return root;
    }
    // 从整个数组开始递归
    let root = BuildTree(nums, 0, nums.length - 1);
    return root;
};

  1. 合并二叉树
  • 给你两棵二叉树: root1 和 root2 。

  • 想象一下,当你将其中一棵覆盖到另一棵之上时,两棵树上的一些节点将会重叠(而另一些不会)。你需要将这两棵树合并成一棵新二叉树。合并的规则是:如果两个节点重叠,那么将这两个节点的值相加作为合并后节点的新值;否则,不为 null 的节点将直接作为新二叉树的节点。
    返回合并后的二叉树。

  • 注意: 合并过程必须从两个树的根节点开始。

  • 提示:

  • 两棵树中的节点数目在范围 [0, 2000] 内

  • -104 <= Node.val <= 104

  • 思路

  • 二叉树使用递归,就要想使用前中后哪种遍历方式?前序,中左右

  • 递归

    1. 确定递归函数的参数和返回值:参数=》两个二叉树的根节点,返回值=》合并之后二叉树的根节点。
    1. 确定终止条件:传入两个树,那么就有两个树遍历的节点t1 和 t2,如果t1 == NULL 了,两个树合并就应该是 t2 了(如果t2也为NULL也无所谓,合并之后就是NULL)。反过来如果t2 == NULL,那么两个数合并就是t1(如果t1也为NULL也无所谓,合并之后就是NULL)。
    1. 确定单层递归的逻辑:重复利用一下t1这个树,t1就是合并之后树的根节点(就是修改了原来树的结构)。那么单层递归中,就要把两棵树的元素加到一起。t1->val += t2->val;
      接下来t1 的左子树是:合并 t1左子树 t2左子树之后的左子树。t1 的右子树:是 合并 t1右子树 t2右子树之后的右子树。最终t1就是合并之后的根节点。
/**
 * 递归思路
1. 参数与返回值
  参数:两棵树的当前节点 root1 和 root2。
  返回值:合并后的新树的根节点。
2. 终止条件
  如果 root1 为空,直接返回 root2(无论 root2 是不是空)。
  如果 root2 为空,直接返回 root1。
3. 单层递归逻辑
  两个节点都不为空时,把它们的值相加,结果存在 root1.val。
  递归合并左子树和右子树,分别赋值给 root1.left 和 root1.right。
  返回合并后的 root1。
 */
var mergeTrees = function(root1, root2) {
    const preOrder = (root1, root2) => {
        // 如果其中一棵树为空,直接返回另一棵树。
        if (!root1) return root2
        if (!root2) return root1
        root1.val += root2.val
        root1.left = preOrder(root1.left, root2.left)
        root1.right = preOrder(root1.right, root2.right)
        return root1
    }
    return preOrder(root1, root2)
}
// 用队列实现了两棵树的同步层序遍历,遇到重叠节点就相加,不重叠的直接挂到结果树上。
var mergeTrees = function(root1, root2) {
    // 如果其中一棵树为空,直接返回另一棵树。
    if (root1 === null) return root2;
    if (root2 === null) return root1;

    let queue = [];
    // 队列中每次存放一对需要合并的节点。
    queue.push(root1);
    queue.push(root2);
    while (queue.length) {
        // 每次从队列取出一对节点,把它们的值相加,结果存在 node1 上。
        let node1 = queue.shift();
        let node2 = queue.shift();
        node1.val += node2.val;
        // 如果两棵树的左孩子都不为空,把它们加入队列,后续继续合并。
        // 如果 node1 没有左孩子,而 node2 有,直接把 node2.left 挂到 node1.left 上。

        // 如果两棵树的右孩子都不为空,把它们加入队列,后续继续合并。
       // 如果 node1 没有右孩子,而 node2 有,直接把 node2.right 挂到 node1.right 上。

        if (node1.left !== null && node2.left !== null) {
            queue.push(node1.left);
            queue.push(node2.left);
        }
        if (node1.right !== null && node2.right !== null) {
            queue.push(node1.right);
            queue.push(node2.right);
        }
        if (node1.left === null && node2.left !== null) {
            node1.left = node2.left;
        }
        if (node1.right === null && node2.right !== null) {
            node1.right = node2.right;
        } 
    }
    // 返回合并后的树(以 root1 为根)。
    return root1;
};

  1. 二叉搜索树中的搜索
  • 给定二叉搜索树(BST)的根节点 root 和一个整数值 val。

  • 你需要在 BST 中找到节点值等于 val 的节点。 返回以该节点为根的子树。 如果节点不存在,则返回 null 。

  • 提示:

  • 树中节点数在 [1, 5000] 范围内

  • 1 <= Node.val <= 107

  • root 是二叉搜索树

  • 1 <= val <= 107

  • 思路

  • 二叉搜索树,根节点的值比所有左子树中的所有值都大,比所有右子树中的所有值都小。可以根据这一特性确定搜索的方向。

  • 递归法

      1. 确定递归函数的参数和返回值:参数=》根节点和要搜索的数值,返回=》以这个搜索数值所在的节点。
      1. 确定终止条件:如果root为空,或者找到这个数值了,就返回root节点。
      1. 确定单层递归的逻辑:因为二叉搜索树的节点是有序的,所以可以有方向的去搜索。如果root.val > val,搜索左子树,如果root.val < val,就搜索右子树,最后如果都没有搜索到,就返回NULL。
// 递归
/**
1. 终止条件
  如果当前节点 root 为空,或者当前节点值等于 val,直接返回当前节点(找到了就返回该节点,没找到就返回 null)。
2. 递归查找
  如果当前节点值大于 val,说明目标值只可能在左子树,递归查找左子树。
  如果当前节点值小于 val,说明目标值只可能在右子树,递归查找右子树。
 */
var searchBST = function(root, val) {
    if (!root || root.val === val) return root
    if (root.val > val) return searchBST(root.left, val)
    if (root.val < val) return searchBST(root.right, val)
}
// 二叉搜索树(BST)查找指定值的迭代写法
/**
1. 循环遍历树节点
  只要当前节点不为空,就一直循环。
2. 比较当前节点值和目标值
  如果当前节点值大于目标值,说明目标值只可能在左子树,root = root.left。
  如果当前节点值小于目标值,说明目标值只可能在右子树,root = root.right。
  如果相等,说明找到了,直接返回当前节点。
3. 循环结束还没找到
  说明树中没有该值,返回 null。
 */
var searchBST = function(root, val) {
    while(root !== null) {
        if (root.val > val) {
            // 目标值比当前节点小,去左子树找
            root = root.left
        } else if (root.val < val) {
            // 目标值比当前节点大,去右子树找
            root = root.right
        } else {
            // 找到了,返回当前节点
            return root
        }
    }
    // 没找到,返回 null
    return null
}

  1. 验证二叉搜索树
  • 给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。

  • 有效 二叉搜索树定义如下:

  • 节点的左子树只包含 小于 当前节点的数。

  • 节点的右子树只包含 大于 当前节点的数。

  • 所有左子树和右子树自身必须也是二叉搜索树。

  • 提示:

  • 树中节点数目范围在[1, 104] 内

  • -231 <= Node.val <= 231 - 1

  • 思路

  • 中序遍历,左中右,它的元素就是有序的,中序遍历得到的数据是单调递增的,则是二叉搜索树。验证二叉搜索树,就相当于变成了判断一个序列是不是递增的了。

  • 二叉搜索树,根节点的值比所有左子树中的所有值都大,比所以右子树中的所有值都小。

  • 递归,中序遍历,将二叉搜索树转变成一个数组,然后比较数组是否是有序的。(二叉搜索树中不能有重复元素)如果不用数组的话也可以在递归遍历的过程中直接判断是否有序,需要注意比较的是 左子树所有节点小于中间节点,右子树所有节点大于中间节点。

  • 递归三部曲:

      1. 确定递归函数,返回值以及参数
      1. 确定终止条件:root为空,也是二叉搜索树
      1. 确定单层递归的逻辑:中序遍历,一直更新maxVal,一旦发现maxVal >= root.val,就返回false,注意元素相同时候也要返回false。
  • 迭代法:用迭代法模拟二叉树中序遍历

// 递归,借助数组
/**
用中序遍历把所有节点值按顺序存到数组 arr。
如果数组不是严格递增的,说明不是有效的二叉搜索树(BST)。
返回 true 表示是有效 BST,false 表示不是。
 */
var isValidBST = function(root) {
    let arr = []
    const buildArr = (root) => {
        if (root) {
            buildArr(root.left)
            arr.push(root.val)
            buildArr(root.right)
        }
    }
    buildArr(root)
    for (let i = 1; i< arr.length; ++i) {
        if (arr[i] <= arr[i - 1]) return false
    }
    return true
}

  1. 二叉搜索树的最小绝对差
  • 给你一个二叉搜索树的根节点 root ,返回 树中任意两不同节点值之间的最小差值 。
  • 差值是一个正数,其数值等于两值之差的绝对值。
  • 提示:
  • 树中节点的数目范围是 [2, 104]
  • 0 <= Node.val <= 105
  • 思路
  • 二叉搜索树是有序的,求最值、差值,可以想成在一个有序数组上求最值,求差值,二叉搜索树采用中序遍历,其实就是一个有序数组。
  • 把二叉搜索树转换成有序数组,然后遍历一遍数组,就统计出来最小差值了。
  • 二叉搜素树中序遍历的过程中,可以直接计算,需要用一个pre节点记录一下cur节点的前一个节点。
// 递归 先转换为有序数组
var getMinimumDifference = function (root) {
    let arr = []
    const buildArr = (root) => {
        if (root) {
            // 中序,左中右
            buildArr(root.left)
            arr.push(root.val)
            buildArr(root.right)
        }
    }
    buildArr(root)
    // 在有序数组 arr 中,找到相邻元素之间的最小差值,即二叉搜索树中任意两不同节点值之间的最小绝对差。
    // 由于二叉搜索树中序遍历得到的数组是有序的,所以最小差值一定出现在相邻元素之间。
   // 这段代码就是遍历有序数组,找出最小的相邻差值。

    // 初始化最小差值为数组最后一个元素(一个较大的初始值)。
    // 从第二个元素开始遍历,依次比较每一对相邻元素的差值。
    let diff = arr[arr.length - 1]
    // ++i 是先加 1 再返回新值,i++ 是先返回原值再加 1,但在 for 循环自增部分,这个返回值不会被用到,所以两者等价。
    for (let i = 1; i< arr.length; i++) {
        // 如果当前相邻元素的差值更小,就更新 diff。
        if (diff > arr[i] - arr[i - 1]) {
            diff = arr[i] - arr[i - 1]
        }
    }
    return diff
}
// 递归,在递归过程中更新最小值
/**
 1. 用中序遍历保证节点值递增,preNode 记录上一个节点。
2. 每次比较当前节点和前一个节点的差值,更新最小值 res。
3. 这样可以在遍历过程中直接得到最小绝对差,无需额外数组。
 */
var getMinimumDifference = function(root) {
    let res = Infinity
    let preNode = null
    // 中序
    const inOrder = (node) => {
        if (!node) return 
        inOrder(node.left)
        if (preNode) {
            // 更新res
            res = Math.min(res, node.val - preNode.val)
        }
        // 记录前一个节点
        preNode = node
        inOrder(node.right)
    }
    inOrder(root)
    return res
}
// 迭代,中序
var getMinimumDifference = function(root) {
    let stack = []
    let cur = root
    let res = Infinity
    let pre = null
    while(cur || stack.length) {
        if(cur) {
            stack.push(cur)
            cur = cur.left
        } else {
            cur = stack.pop()
            if(pre) res = Math.min(res, cur.val - pre.val)
            pre = cur
            cur = cur.right
        }
    }
    return res
}

  1. 二叉搜索树中的众数
  • 给你一个含重复值的二叉搜索树(BST)的根节点 root ,找出并返回 BST 中的所有 众数(即,出现频率最高的元素)。
  • 如果树中有不止一个众数,可以按 任意顺序 返回。
  • 假定 BST 满足如下定义:
  • 结点左子树中所含节点的值 小于等于 当前节点的值
  • 结点右子树中所含节点的值 大于等于 当前节点的值
  • 左子树和右子树都是二叉搜索树
  • 提示:
  • 树中节点的数目在范围 [1, 104] 内
  • -105 <= Node.val <= 105
  • 思路
  • 如果不是二叉搜索树,最直观的方法一定是把这个树都遍历了,用map统计频率,把频率排个序,最后取前面高频的元素的集合。
  • 二叉搜索树,中序遍历就是有序的。遍历有序数组的元素出现频率,从头遍历,那么一定是相邻两个元素作比较,然后就把出现频率最高的元素输出就可以了。若是不借助数组,直接在树上进行比较,弄一个指针pre指向前一个节点,这样每次cur(当前节点)才能和pre(前一个节点)作比较。而且初始化的时候pre = NULL,这样当pre为NULL时候,我们就知道这是比较的第一个元素。
// 递归
// 借助额外空间 mao
var findMode = function(root) {
    // 使用递归中序遍历
    let map = new Map();
    // 1. 确定递归函数以及函数参数
    const traverTree = function(root) {
        // 2. 确定递归终止条件
        if(root === null) {
            return ;
        }
        traverTree(root.left);
         // 3. 单层递归逻辑
        map.set(root.val,map.has(root.val)?map.get(root.val)+1:1);
        traverTree(root.right);
    }
    traverTree(root);
    //上面把数据都存储到map
    //下面开始寻找map里面的
    // 定义一个最大出现次数的初始值为root.val的出现次数
    let maxCount = map.get(root.val);
    // 定义一个存放结果的数组res
    let res = [];
    // value 表示当前元素 key 在二叉搜索树中出现的次数。
   // maxCount 表示目前为止出现次数的最大值。
    for(let [key,value] of map) {
        // 如果当前值等于最大出现次数就直接在res增加该值
        if(value === maxCount) {
            res.push(key);
        }
        // 如果value的值大于原本的maxCount就清空res的所有值,因为找到了更大的
        if(value>maxCount) {
            res = [];
            maxCount = value;
            res.push(key);
        }
    }
    return res;
};

// 不使用额外空间,利用二叉树性质,中序遍历(有序)
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;
};


  1. 二叉树的最近公共祖先
  • 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
  • 百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
  • 提示:
  • 树中节点数目在范围 [2, 105] 内。
  • -109 <= Node.val <= 109
  • 所有 Node.val 互不相同 。
  • p != q
  • p 和 q 均存在于给定的二叉树中。
  • 思路
var lowestCommonAncestor = function(root, p, q) {
    // 使用递归的方法
    // 需要从下到上,所以使用后序遍历
    // 1. 确定递归的函数
    const travelTree = function(root,p,q) {
        // 2. 确定递归终止条件
        if(root === null || root === p || root === q) {
            return root;
        }
        // 3. 确定递归单层逻辑
        let left = travelTree(root.left,p,q);
        let right = travelTree(root.right,p,q);
        if(left !== null && right !== null) {
            return root;
        }
        if(left === null) {
            return right;
        }
        return left;
    }
   return  travelTree(root,p,q);
};




参考&感谢各路大神

posted @ 2025-06-13 22:02  安静的嘶吼  阅读(8)  评论(0)    收藏  举报