LeetCode之二叉搜索树系列
昨天我的二叉树系列突然上榜了(《数据结构与算法领域内容榜》),最高18名,很意外,感谢官方大大 #CSDN 的支持。
计划着按照la哥的建议,先干二叉树,所以前面做了一下简单的二叉树,捡回了一些知识,直观感受就是做这类题至少有了一个大致的思路,普通的二叉树、平衡二叉树了解了一些,今天来干二叉搜索树。
二叉搜索树( BST )是神马东东?
我的理解就是,一颗二叉树的中序遍历结果是一个递增序列,这就是二叉搜索树
三道题
1. 有序数组转高度平衡的 BST
2. 有序链表转高度平衡的 BST
3. BST 中的搜索
题目描述移步力扣官网哈
1. 有序数组转高度平衡的 BST
给一个严格递增的数组(符合我上面说的,中序遍历后是一个递增序列,现在就是把这个序列给变回去,变成BST),我当时的第一想法是,既然要满足二叉搜索树的定义,那么数组的最中间的元素一定是根节点(或者与中间元素的距离等于1),因为还要满足高度平衡,高度差不能超过1,这样根节点有了。
左子树和右子树怎么办?
左右子树不还是一样,一样要找根节点,只要是根节点,那就和上面的找法没区别,所以相当于是数组不断的被分隔成两部分,这不就是递归该出场了吗?(分治也可以)
var sortedArrayToBST = function(nums) {
function insertNode(nums,left,right){
//左边大于右边,说明已经没有后续元素需要插入了
if(left > right){
return null;
}
//取中间位置的做法还有 mid = (left + right) >>> 1;
let mid = Math.floor((left + right) / 2);
//如上所说,只要是根节点,就以中间元素作为它的值
let root = new TreeNode(nums[mid]);
//划分数组左区间构建左子树
root.left = insertNode(nums,left,mid - 1);
//划分数组右区间构建右子树
root.right = insertNode(nums,mid + 1,right);
return root;
}
return insertNode(nums,0,nums.length - 1);
};
2. 有序链表转高度平衡的 BST
我相信看到这个题,大家一定和我一样聪明(我不是)想着直接把有序链表转成有序数组,就神奇的变成上一个题了,但是呢,这确实有点投机取巧,所以当然还有其他办法呀
提前剧透:方法二:快慢指针,方法三:中序遍历
(我怎么可能想出来呢,当然是各位大佬的意思,我只是来说说我对这两种方法的理解的)
方法二三来自
作者:xiao_ben_zhu
链接:https://leetcode-cn.com/problems/convert-sorted-list-to-binary-search-tree/solution/shou-hua-tu-jie-san-chong-jie-fa-jie-
// 方法一(转数组,就不解释了)
var sortedListToBST = function(head) {
let nums= [];
while(null !== head){
nums.push(head.val);
head = head.next;
}
let insertNode = (nums,low,high)=>{
if(low > high) return null;
let mid = Math.floor((low + high) / 2);
let root = new TreeNode(nums[mid]);
root.left = insertNode(nums,low,mid - 1);
root.right = insertNode(nums,mid + 1,high);
return root;
}
return insertNode(nums,0,nums.length - 1)
};
// 方法二(快慢指针)
var sortedListToBST = function(head) {
//如果是空树或者当前节点是叶子节点,返回null
if (head == null) return null;
//维护快慢指针,初始值是head
let slow = head;
let fast = head;
// 保存slow的前一个节点,这个慢指针前驱很关键
//作用就相当于是把链表切成左右两部分的刀,每次都是它切,每次slow找到中间节点,它就得切一次,保证区间再次化小,递归构建子树
let preSlow;
//快慢指针真的妙。你看啊,同样的距离,每次我走两步,你走一步,那等我走完,你是不是刚好在中间呢,妙!
while (fast && fast.next) {//fast走到尾部就是找到中间节点的时刻
preSlow = slow; // 保存当前slow
slow = slow.next; // slow走一步
fast = fast.next.next; // fast走两步
}
const root = new TreeNode(slow.val); // 根据slow指向的节点值(就是链表的中间节点),构建节点
if (preSlow != null) { // 如果preSlow有值,即slow左边有节点,需要构建左子树
preSlow.next = null; // 切断preSlow和中点slow
//这个preslow就相当于是切完了,然后再重新构建一个二叉搜索树,只不过呢,这棵树是刚刚根节点的左子树
root.left = sortedListToBST(head); // 然后递归构建左子树(就是重复构建后面每个节点的左子树)
}
root.right = sortedListToBST(slow.next); // 递归构建右子树,这个就是右半部分,用来重复构建每个节点的右子树
return root;
};
// 方法三(中序遍历)
不需要找中间节点,遇到哪个节点,就把哪个节点当做根节点,就是一直构建一个只有三个节点的二叉树,然后,构建完一颗就连上下一个合适的根节点
var sortedListToBST = function(head) {
if(head === null)return null;
let h = head;//构建节点的关键指针
let len = 0;
while(head){
len ++;
head = head.next;
}
const buildBST = (start, end)=>{
if(start > end) return null;//当不能再二分,就说明这是最底部的空节点
let mid = (start + end) >>> 1;//只是单纯的二分一下,用于分别构建左右子树
let left = buildBST(start,mid - 1);//按照中序遍历的顺序,先构建左子树
let root = new TreeNode(h.val);//创建根节点
h = h.next;//不断后移,为每棵小树创建节点
root.left = left;//连上左子树
root.right = buildBST(mid + 1,end);//连上右子树
return root;
}
return buildBST(0,len - 1);
};
3. BST 中的搜索
这就是在一个有序序列里找数字的游戏,很简单,假如是递增序列,每次比较中间元素mid,比mid大的丢到后半区间找,比mid小的丢到前半区间找,思路应该很清晰
var searchBST = function(root, val) {
while(root !== null){//只要当前进入的根节点不是空的,就说明我还有可能!!!
if(root.val === val){
return root;
}else if(root.val > val){//目标值比根节点小,丢到左区间
return searchBST(root.left, val);
}else if(root.val < val){//目标值比根节点大,丢到右区间
return searchBST(root.right, val);
}
}
return null
};
本来还想再写两个个题的,但是能力有限,我自己都没明白呢,就不瞎写了,下次见(下次直接加在这里面,所以建议收藏本文哦)!