二分查找的好题
Posted 李嘉图杂文
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了二分查找的好题相关的知识,希望对你有一定的参考价值。
题目:峰值元素是指其值大于左右相邻值的元素。
给你一个输入数组 nums,找到峰值元素并返回其索引。数组可能包含多个峰值,在这种情况下,返回 任何一个峰值 所在位置即可。
你可以假设nums[-1] = nums[n] = -∞ 。
示例 1:输入:nums = [1,2,3,1]输出:2
解释:3 是峰值元素,你的函数应该返回其索引 2。
示例 2:输入:nums = [1,2,1,3,5,6,4]输出:1 或 5
解释:你的函数可以返回索引 1,其峰值元素为 2;或者返回索引 5, 其峰值元素为 6。
解题思路:
-
遍历访问,找到第一个peak就返回。
class Solution {
public int findPeakElement(int[] nums) {
int ans = -1;
for(int i=0;i<nums.length;i++){
if(isPeak(nums,i)){
ans = 0;
return i;
}
}
}
private boolean isPeak(int[] nums,int cur){
if(cur == 0||cur==nums.length-1) return false;
if((nums[cur-1]<nums[cur])&&(nums[cur+1]<nums[cur])) return true;
else return false;
}
} -
二分查找
二分查找的前提是,序列必须是有规律的,可以保证我们舍弃掉一半后,剩下 的部分一定包含要寻找的target。
分析这个序列,我们从局部到整体来看。
首先确定的一点就是,我们判断一个点是不是peak,要和它的相邻元素比较。
-
局部观察:
我们随便画一个峰,不难发现:
peak的左边,所有的元素都比它后面紧邻的元素小。
peak的右边,所有的元素都比它后面紧邻的元素大。
至此,我们可以有一个初步的判断:
while(left<right){
int mid = left+(right-left);
if(isPeak(nums,mid)) return mid;
/*peak在num[i]的右边,即num[i]在上坡的那一面*/
if(nums[mid]<nums[i+1]) left = mid;
/*peak在num[i]的左边,即num[i]在下坡的那一面*/
else right = mid;
} -
整体分析:
这样的设想,并不会失败。
当这个线段是一条单调的线段时,我们会找到最左边或者最右边的元素。这是符合定义的,因为两侧的元素被视为无穷小。
当线段存在peak时,总能找到peak,找到的peak可能是线段中间的某一点,也可能是两头中的某一个。
-
细节:
如果按照下面的代码执行:
if(nums[mid]<nums[i+1]) left = mid;
else right = mid;考虑数组[1,2]
//第一轮
left = 0;
right = 1;
mid = 1;
//第二轮
left = 0;
right = 1;
mid = 1;
···我们发现在收尾(仅剩两个元素)的时候,会陷入无尽的循环,因为left和right指针不动了。
收尾的情况可以用数组[2,1]和[1,2]来概括。
那么我们在细节上,应该让
right = mid-1
还是left = mid+1
?如果采用
right = mid-1
的话。考虑数组[1,2]
//第一轮
left = 0;
right = 1;
mid = 0;
//第二轮
left = 0;
right = 0;
mid = 0;
//结果出错,我们把真正的peak舍弃了如果采用
left = mid+1
。考虑数组[1,2]
//第一轮
left = 0;
right = 1;
mid = 0;
//第二轮
left = 1;
right = 1;
mid = 1;
//结果对了考虑数组[2,1]
//第一轮
left = 0;
right = 1;
mid = 0;
//第二轮
left = 0;
right = 0;
mid = 0;
//结果还是对的所以,最终实现的代码是:
class Solution {
public int findPeakElement(int[] nums) {
int ans = -1;
int left = 0;
int right = nums.length -1;
while(left<right){
int mid = left+(right-left)/2;
if(isPeak(nums,mid)){
ans = 0;
return mid;
}
if(nums[mid]<nums[mid+1]){
left = mid+1;
}else{
right = mid;
}
}
if(ans < 0){
return (nums[0]>nums[nums.length-1])?0:nums.length-1;
}
return left;
}
private boolean isPeak(int[] nums,int cur){
if(cur == 0||cur==nums.length-1) return false;
if((nums[cur-1]<nums[cur])&&(nums[cur+1]<nums[cur])) return true;
else return false;
}
}
二分法套路
-
找序列规律,判定舍弃的条件。
-
考虑指针移动的策略:
收尾是关键点。
-
模板:
while(left<right){
if(/*查找成功*/) {/*成功了*/}
if(/*target在右边*/){left = mid+1;}
else{right = mid;}
}
以上是关于二分查找的好题的主要内容,如果未能解决你的问题,请参考以下文章