山脉数组中查找目标值--二分搜索

0x01.问题

给你一个 山脉数组 mountainArr,请你返回能够使得 mountainArr.get(index) 等于 target 最小 的下标 index 值。

如果不存在这样的下标 index,就请返回 -1

何为山脉数组?如果数组 A 是一个山脉数组的话,那它满足如下条件:

首先A.length >= 3

其次,在 0 < i < A.length - 1 条件下,存在 i 使得:

  • A[0] < A[1] < ... A[i-1] < A[i]
  • A[i] > A[i+1] > ... > A[A.length - 1]

你将 不能直接访问该山脉数组,必须通过 MountainArray 接口来获取数据:

  • MountainArray.get(k) - 会返回数组中索引为k 的元素(下标从 0 开始)
  • MountainArray.length() - 会返回该数组的长度

注意:

MountainArray.get 发起超过 100 次调用的提交将被视为错误答案。

示例 1:

输入:array = [1,2,3,4,5,3,1], target = 3 输出:2 解释:3 在数组中出现了两次,下标分别为 2 和
5,我们返回最小的下标 2。

示例 2:

输入:array = [0,1,2,4,2,1], target = 3 输出:-1 解释:3 在数组中没有出现,返回 -1。

提示:

  • 3 <= mountain_arr.length() <= 10000
  • 0 <= target <= 10^9
  • 0 <= mountain_arr.get(index) <= 10^9
 * // This is MountainArray's API interface.
 * // You should not implement it, or speculate about its implementation
 * interface MountainArray {
 *     public int get(int index) {}
 *     public int length() {}
 * }
public int findInMountainArray(int target, MountainArray mountainArr)

0x02.简要分析思路

看一下题目,大概是这个样子的:

  • 山脉数组其实就是数组中有一个值,满足在左边单调增,右边单调减。
  • 不能够直接访问数组,必须通过接口。
  • 不能访问超过100次,但是数组长度有10000,所以,说白了,就是不允许我们使用普通的顺序查找,限制了时间复杂度。

将这些条件综合一下,就只有一个思路了,二分搜索,为什么想到二分搜索呢?

  • 第一,数组局部有序,山顶左边升序,右边降序。
  • 第二,搜索次数有限,只有二分搜索能够达到这种目标。

那么我们应该如何对局部去搜索呢?

  • 第一,局部有序肯定不能去全局的搜索一个值,只能去寻找局部有序的分界线。
  • 第二,这种局部有序中的搜索一定是分别在有序的部分里搜索的。

综合一下这两个判断条件,我们可以发现,我们需要这个分界线的位置,利用这个位置对两边分别进行二分搜索,同时,这个分界线又可以根据全局的二分搜索去找到。

也就是说,我们需要进行三次二分搜索:

  1. 全局搜索,得到区间分界线的下标。
  2. 在左边有序部分进行搜索。
  3. 如果左边没有搜索到,就继续在右边进行搜索。

我们来看一下具体是如何去进行二分搜索的。
二分搜索的三大要点:

  • 循环结束条件。
  • 中间点选取方式。
  • 边界收缩行为。

如果三种方式选取的不一样,最后得到的结果的值会在不同的地方取得。

对于第一次搜索,我们的目标是查找分界点,实质上,还是查找第一个满足条件(单调减)的下标值,这种查找第一个满足条件的问题,最常用的循环条件就是while(left<right)

  • 对于这种循环条件,退出循环的条件是left=right,说明最后的答案左右都可以。
  • 然后确定一下边界收缩的方式,对于搜索边界值而言,我们无妨将边界放到左边,即分为[left, mid][mid + 1, right],此时中点选取方式为mid=left+(right-left)/2,若分到右边,则选取为mid=left+(right-left+1)/2

对于第二和第三次搜索,是具体的搜索指定的值了,一般就采用while(left<=right),可以把区间分为三个部分:[left,mid-1],[mid],[mid+1,right]

确定了这些,就可以很快速的写出二分的代码了,具体的细节看代码。

0x03.解决代码–二分搜索

class Solution {
    public int findInMountainArray(int target, MountainArray mountainArr) {
        int left=0,right=mountainArr.length()-1;
        //第一次二分搜索,搜索山顶
        while(left<right){
            int mid=left+(right-left)/2;
            if(mountainArr.get(mid)<mountainArr.get(mid+1)){
                left=mid+1;
            }else{
                right=mid;
            }
        }
        int top=left;
        left=0;
        right=top;
        int index=-1;
        //第二次二分搜索,搜索山顶左边的区间
        while(left<=right){
            int mid=left+(right-left)/2;
            int midValue=mountainArr.get(mid);
            if(midValue==target){
                return mid;
            }else if(midValue<target){
                left=mid+1;
            }else{
                right=mid-1;
            }
        }     
        left=top+1;
        right=mountainArr.length()-1;
        //第三次二分搜索,搜索山顶右边的区间
        while(left<=right){
            int mid=left+(right-left)/2;
            int midValue=mountainArr.get(mid);
            if(midValue==target){
                return mid;
            }else if(midValue>target){
                left=mid+1;
            }else{
                right=mid-1;
            }
        }
        return -1;
    }
}

ATFWUS --Writing By 2020–04-29

  • 24
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ATFWUS

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值