本篇笔记在于进一步巩固二分查找递归模板和非递归模板,同时扩充二分查找的变式问题,从而深层次的掌握二分查找的边界条件,形成针对变式问题自主分析边界条件,灵活运用模板解决问题!同时,该部分将树和二分查找相结合,引出二叉排序树概念,并对判断二叉排序树与中序遍历知识点相结合,温故知新!
1.二分查找变式问题
1.1山脉数组的封顶索引
题目见LeetCode852,其描述为在一个长度大于3的数组中,前半部分存储的元素递增,后半部分存储的元素递减,求该数组的最大元素(峰),其中峰的位置不在数组的左右两端。
题目分析:对于数组的前半段,一定满足arr[i] < arr[i+1],后半段一定满足arr[i] > arr[i+1]。当找到第一个发生变化的arr[i]就是数组的峰。厘清思路,直接上代码!
public static int myPeakIndexInMountainArray(int[] arr) {
int n = arr.length;
int index = -1;
for (int i = 0; i < n - 1; i++) {
if(arr[i] > arr[i + 1]) {
index = i;
break;
}
}
return index == -1 ? n - 1 : index;
}
是的,这样的方法可以解决,但是时间复杂度是O(n),能有时间复杂度更低的方法吗?答:二分查找,时间复杂度为O(logn).
二分查找,模板是第一,边界条件是关键!那么边界条件是什么呢?
考虑中间mid节点,自然分成三种情况,找到峰,峰左侧和峰右侧。
找到峰:arr[mid]>arr[mid-1] && arr[mid]>arr[mid+1];
峰左侧:arr[mid-1]<arr[mid] && arr[mid]<arr[mid+1];
峰右侧:arr[mid-1]>arr[mid] && arr[mid]>arr[mid-1];
当找到峰的时候,返回mid;当在峰左侧的时候,low=mid+1;当在峰右侧的时候,high=low+1;
厘清逻辑,直接上代码!
public static int myPeakIndexInMountainArray2(int[] arr){
int low = 0;
int high = arr.length-1;
while(low < high){
int mid = low +((high - low) >> 1);
if(arr[mid] > arr[mid - 1] && arr[mid] > arr[mid + 1]) return mid;
if(arr[mid] > arr[mid - 1] && arr[mid] < arr[mid + 1]) low = mid + 1;
if(arr[mid] < arr[mid - 1] && arr[mid] > arr[mid + 1]) high = mid - 1;
}
return low;
}
1.2旋转数字的最小数字
题目见LeetCode153
什么是旋转数字?在之前的链表和数组的旋转题目中提及过,将数组(链表)向一个方向旋转即可得到旋转数组。
那么,旋转数组有什么特性?答:以最小元素为分界点,最小元素左侧各元素依次递增,最小元素右侧各元素也是依次递增,左侧元素整体大于右侧元素,如图(来自鱼骨头哥):
那么,我们就需要利用这个特性来找最小元素!也就是二分查找的边界条件!
比较mid和high的大小,当mid<high说明,mid到high之间的元素全部在最小元素的右侧,那么下一查找,让high=mid(因为可能此时mid就是最小元素);当mid>high说明,low到high之间的元素全部在最小元素的的左侧,那么下一次查找,让low=mid+1。厘清逻辑,直接上代码!
public static int myFindMin(int[] nums) {
int low = 0;
int high = nums.length -1;
while(low < high){
int mid = low + ((high - low) >> 1);
if(nums[mid] == nums[high]) return nums[mid];
else if(nums[mid] < nums[high]) high = mid;
else low = mid + 1;
}
return nums[low];
}
1.3找缺失数字
题目见剑指 Offer 53Ⅱ ,描述为找到一个从0开始的连续序列中的缺失数字。
如何二分查找?答:看二分查找边界条件!
假如序列从i位置开始缺失,那么0到i-1位置均满足nums[i] = i;i到n均满足nums[i]!=i。那么,我们找到第一个nums[i]!=i的元素,或者找到最后一个nums[i]=i的元素的下一个位置,就可以找到最终缺失的元素!这里有巨坑!!!我们采用方式二!
找到中间mid节点,当nums[mid]!=mid说明,mid右侧元素全部在缺失数字的右侧,应该在左侧查找缺失数字,因此high=mid-1;当nums[mid]==mid说明,mid左侧元素全部在缺失数字的左侧,应该在右侧查询缺失数字,因此low=mid+1。理清逻辑,直接上代码!
public static int mySolve2(int[] a) {
int low = 0;
int high = a.length - 1;
while(low <= high){
int mid = low + ((high - low) >> 1);
//左半部分在缺失数字之前
if(a[mid] == mid) low = mid + 1;
//右半部分在缺失数字之后
else high = mid - 1;
}
return low;
}
1.4优化求平方根
题目见剑指Offer,描述为最快方式找x的平方根(四舍五入),怎么做?
边界条件:mid * mid == x,找到平方根;mid * mid < x,右侧找平方根;mid * mid > x,左侧找平方根。厘清思路,直接上代码!
public static int mySqrt(int x) {
int low = 1, high = x;
while(low < high){
int mid = low + ((high - low) >> 1);
if(mid * mid == x) return mid;
else if (mid * mid < x) low = mid + 1;
else high = mid - 1;
}
return low;
}
2.二叉排序树与中序遍历
2.1二叉排序树搜索特定值
题目见LeetCode700。
很简单,直接上代码!(递归和迭代两种实现)
public static TreeNode mySearchBST(TreeNode root, int val) {
if(root == null || root.val == val) return root;
return val < root.val ? mySearchBST(root.left, val) : mySearchBST(root.right, val);
}
public static TreeNode mySearchBST2(TreeNode root, int val) {
while(root != null && root.val != val){
root = val < root.val ? root.left : root.right;
}
return root;
}
2.2验证二叉排序树
如何验证二叉排序树?依据二叉排序树的特性,中序遍历序列必然从小到大顺序排列,可以先验证左子树是否是二叉排序树,在验证当前节点是否大于左子树中最后遍历的节点,在验证右子树是否是二叉排序树即可!厘清逻辑,直接上代码!
long pre = Long.MIN_VALUE;
public static boolean myIsValidBST(TreeNode root) {
if(root == null) return true;
if(!myIsValidBST(root.left)) return false;
if(pre >= root.val) return false;
pre = root.val;
return myIsValidBST(root.right);
}
OK,《算法通关村第九关——二分查找白银挑战笔记》结束,喜欢的朋友三联加关注!关注鱼市带给你不一样的算法小感悟!(幻听)
再次,感谢鱼骨头教官的学习路线!鱼皮的宣传!小y的陪伴!ok,拜拜,第九关第三幕见!