530.二叉搜索树的最小绝对差
题目链接
思路
先中序遍历二叉树,得到一个数组,然后对数组进行处理,求其中元素的差值的绝对值的最小值
这样就可以把二叉树问题转换为数组问题
转化之后的数组是递增的,所以只需要算出相邻项之间的差值,然后去最小值即可
代码如下:
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.二叉搜索树中的众数
题目链接
思路
和上一题一样的,先转化为数组
再求一个递增数组中出现最多的元素
求出现最多的元素这一步,可以用哈希表
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. 二叉树的最近公共祖先
题目链接
思路
用回溯,后序遍历就是天然的回溯
递归三部曲:
-
确定递归函数的返回值和参数
参数是root,左节点,右节点
返回值:
如果碰到p或者q,就把p,q返回了,否则,返回空
-
确定终止条件
遍历到null时,返回null,否则,返回p或者q
-
确定单层递归逻辑
left = 左边遍历结果
right = 右边遍历结果
单层递归逻辑处理
这里需要注意的是回溯的原理
代码如下:
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. 二叉搜索树的最近公共祖先
题目链接
思路
这题是二叉搜索树,相比于二叉树,简单了许多
根据二叉搜索树的特性,只需要找到在[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.二叉搜索树中的插入操作
题目链接
思路
看上去有点难,实际上就是先找到可以插入的节点位置,然后把原本是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.删除二叉搜索树中的节点
题目链接
思路
删除就涉及到树的结构的调整了,有点类似于插入操作的无返回值的情况
递归三部曲:
-
确定参数和返回值
参数就是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. 修剪二叉搜索树
题目链接
思路
递归三部曲:
-
确定参数和返回值
参数就是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;
};
这一题看上去简单,但是需要好好理解,可以画图来进行辅助