提示:有时候,我感觉自己一辈子活在两个闹钟之间,早上的第一次闹钟,以及5分钟之后的第二次闹钟。 --奥利弗·萨克斯《意识的河流》
每个专题都有简单题,有难的题目。这里就介绍两道有挑战的题,一道是关于二叉搜索树的,一道是从两个数组中寻找中位数的。
参考题目介绍:108. 将有序数组转换为二叉搜索树 - 力扣(LeetCode)
理论上如果要构造二叉搜索树,可以以升序序列中的任意一个元素作为根节点,以该元素左边的升序序列构建左子树,以该元素的右边的升序序列构建右子树,这样得到的树就是一棵二叉搜索树。本体要求高度平衡,一次我们需要选择升序序列的中间元素作为根节点,其本质就是二分查找的过程。
话不多说,看代码:
/**
* 升序数组转二叉搜索树
* @param nums
* @return
*/
public static TreeNode sortedArrayToBST(int[] nums) {
return dfs(nums,0,nums.length - 1);
}
private static TreeNode dfs(int[] nums, int left, int right) {
if (left > right){
return null;
}
// 处理节点 总是选择中间位置左边的数字作为根节点
int mid = left + ((right - left) >> 1);
TreeNode root = new TreeNode(nums[mid]);
root.left = dfs(nums,left,mid - 1);
root.right = dfs(nums,mid + 1,right);
return root;
}
除了通过数组构造,是否可以通过一个个插入的方式来实现呢?当然可以。那如果从中间删除一个元素呢?
推荐一下题目:⭐⭐⭐⭐⭐
701. 二叉搜索树中的插入操作 - 力扣(LeetCode)
450. 删除二叉搜索树中的节点 - 力扣(LeetCode)
参考题目介绍:4. 寻找两个正序数组的中位数 - 力扣(LeetCode)
如何把时间复杂度降低至O(log(m+n))呢?如果对时间复杂度的要求是log,通常都要考虑二分,快排或者堆三个方面。而对于有序的序列,通常要优先考虑使用二分来解决。
如果要使用二分,核心问题是基于什么规则将数据砍掉一半。而本题是两个序列,所以我们的核心问题是如何从两个序列中分别砍半,图示k = (m + n) >> 1;
根据中位数的定义,当 m + n是奇数时,中间的两个序列数组的第(m + n) >> 1个元素,当 m + n是偶数时,中间是两个有序数组中的第(m + n) >> 1个元素和第((m + n) >> 1)+ 1的平均值。因此,这道题可以转换为寻找两个有序数组中的第k小的数,其中k为(m + n) >> 1或者((m + n) >> 1)+ 1
假设两个有序数组分别是LA和LB。要找到第k个元素我饿们可以比较LA[k / 2 - 1]和LB[k / 2 -1]。由于LA[k / 2 - 1]和LB[k / 2 -1]的前面分别是LA[k / 2 - 2]和LB[k / 2 -2],即k / 2 - 1个元素,对于LA[k / 2 - 1]和LB[k / 2 -1]中的最小值,最多只会有[k / 2 - 1] + [k / 2 -1] <= k - 2个元素比它小,那么它不就是我们要的第k小的数了。
因此我们可以总结一下:
注意一下边界处理:
分析了这么多,怎么写这个代码呢?
/**
* 两个有序数组找中间值
* @param nums1
* @param nums2
* @return
*/
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int length1 = nums1.length,length2 = nums2.length;
int totalLength = length1 + length2;
if (totalLength % 2 == 1){
int midIndex = totalLength >> 1;
double median = getKthElement(nums1,nums2,midIndex + 1);
return median;
}else{
int midIndex1 = (totalLength >> 1) - 1;
int midIndex2 = (totalLength >> 1);
double median = (getKthElement(nums1,nums2,midIndex1 + 1) + getKthElement(nums1,nums2,midIndex2 + 1)) / 2.0;
return median;
}
}
private double getKthElement(int[] nums1, int[] nums2, int k) {
int length1 = nums1.length,length2 = nums2.length;
int index1 = 0, index2 = 0;
int kthElement = 0;
while(true){
// 边界问题
if (index1 == length1){
return nums2[index2 + k - 1];
}
if(index2 == length2){
return nums1[index1 + k - 1];
}
if (k == 1){
return Math.min(nums1[index1],nums2[index2]);
}
// 正常情况
int half = k >> 1;
int newIndex1 = Math.min(index1 + half,length1) - 1;
int newIndex2 = Math.min(index2 + half,length2) - 1;
int pivot1 = nums1[newIndex1],pivot2 = nums2[newIndex2];
if(pivot1 <= pivot2){
k -= (newIndex1 - index1 + 1);
index1 = newIndex1 + 1;
}else{
k -= (newIndex2 - index2 + 1);
index2 = newIndex2 + 1;
}
}
}
提示:二分进阶;二分搜索;中序遍历;递归算法;分治思想