Day17-18-二叉树-leetcode654,617,700,98 - 530,501,236
- 最大二叉树
-
给定一个不重复的整数数组 nums 。 最大二叉树 可以用下面的算法从 nums 递归地构建:
-
创建一个根节点,其值为 nums 中的最大值。
-
递归地在最大值 左边 的 子数组前缀上 构建左子树。
-
递归地在最大值 右边 的 子数组后缀上 构建右子树。
-
返回 nums 构建的 最大二叉树 。
-
提示:
-
1 <= nums.length <= 1000
-
0 <= nums[i] <= 1000
-
nums 中的所有整数 互不相同
-
思路
-
构造树,一般采用的是前序遍历,因为先构造中间节点,然后递归构造左子树和右子树。
-
- 确定递归函数的参数和返回值,参数=》存放元素的数组,返回=》该数组构造的二叉树的头节点
-
- 确定终止条件,提示1 <= nums.length <= 1000,即输入数组大小大于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;
};
- 合并二叉树
-
给你两棵二叉树: root1 和 root2 。
-
想象一下,当你将其中一棵覆盖到另一棵之上时,两棵树上的一些节点将会重叠(而另一些不会)。你需要将这两棵树合并成一棵新二叉树。合并的规则是:如果两个节点重叠,那么将这两个节点的值相加作为合并后节点的新值;否则,不为 null 的节点将直接作为新二叉树的节点。
返回合并后的二叉树。 -
注意: 合并过程必须从两个树的根节点开始。
-
提示:
-
两棵树中的节点数目在范围 [0, 2000] 内
-
-104 <= Node.val <= 104
-
思路
-
二叉树使用递归,就要想使用前中后哪种遍历方式?前序,中左右
-
递归
-
- 确定递归函数的参数和返回值:参数=》两个二叉树的根节点,返回值=》合并之后二叉树的根节点。
-
- 确定终止条件:传入两个树,那么就有两个树遍历的节点t1 和 t2,如果t1 == NULL 了,两个树合并就应该是 t2 了(如果t2也为NULL也无所谓,合并之后就是NULL)。反过来如果t2 == NULL,那么两个数合并就是t1(如果t1也为NULL也无所谓,合并之后就是NULL)。
-
- 确定单层递归的逻辑:重复利用一下t1这个树,t1就是合并之后树的根节点(就是修改了原来树的结构)。那么单层递归中,就要把两棵树的元素加到一起。t1->val += t2->val;
接下来t1 的左子树是:合并 t1左子树 t2左子树之后的左子树。t1 的右子树:是 合并 t1右子树 t2右子树之后的右子树。最终t1就是合并之后的根节点。
- 确定单层递归的逻辑:重复利用一下t1这个树,t1就是合并之后树的根节点(就是修改了原来树的结构)。那么单层递归中,就要把两棵树的元素加到一起。t1->val += t2->val;
/**
* 递归思路
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;
};
- 二叉搜索树中的搜索
-
给定二叉搜索树(BST)的根节点 root 和一个整数值 val。
-
你需要在 BST 中找到节点值等于 val 的节点。 返回以该节点为根的子树。 如果节点不存在,则返回 null 。
-
提示:
-
树中节点数在 [1, 5000] 范围内
-
1 <= Node.val <= 107
-
root 是二叉搜索树
-
1 <= val <= 107
-
思路
-
二叉搜索树,根节点的值比所有左子树中的所有值都大,比所有右子树中的所有值都小。可以根据这一特性确定搜索的方向。
-
递归法
-
- 确定递归函数的参数和返回值:参数=》根节点和要搜索的数值,返回=》以这个搜索数值所在的节点。
-
- 确定终止条件:如果root为空,或者找到这个数值了,就返回root节点。
-
- 确定单层递归的逻辑:因为二叉搜索树的节点是有序的,所以可以有方向的去搜索。如果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
}
- 验证二叉搜索树
-
给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。
-
有效 二叉搜索树定义如下:
-
节点的左子树只包含 小于 当前节点的数。
-
节点的右子树只包含 大于 当前节点的数。
-
所有左子树和右子树自身必须也是二叉搜索树。
-
提示:
-
树中节点数目范围在[1, 104] 内
-
-231 <= Node.val <= 231 - 1
-
思路
-
中序遍历,左中右,它的元素就是有序的,中序遍历得到的数据是单调递增的,则是二叉搜索树。验证二叉搜索树,就相当于变成了判断一个序列是不是递增的了。
-
二叉搜索树,根节点的值比所有左子树中的所有值都大,比所以右子树中的所有值都小。
-
递归,中序遍历,将二叉搜索树转变成一个数组,然后比较数组是否是有序的。(二叉搜索树中不能有重复元素)如果不用数组的话也可以在递归遍历的过程中直接判断是否有序,需要注意比较的是 左子树所有节点小于中间节点,右子树所有节点大于中间节点。
-
递归三部曲:
-
- 确定递归函数,返回值以及参数
-
- 确定终止条件:root为空,也是二叉搜索树
-
- 确定单层递归的逻辑:中序遍历,一直更新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
}
- 二叉搜索树的最小绝对差
- 给你一个二叉搜索树的根节点 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
}
- 二叉搜索树中的众数
- 给你一个含重复值的二叉搜索树(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;
};
- 二叉树的最近公共祖先
- 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
- 百度百科中最近公共祖先的定义为:“对于有根树 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);
};

浙公网安备 33010602011771号