二分查找算法的万能公式(LeetCode35、704、1095)

二分查找常常用于有序数组的查找操作。当然如果是一个问题,数组的顺序满足特定条件(不仅仅是升序或者降序,也可以是先升后降或先降后升),就可以通过逐步排查,缩小问题的规模的方式找到,这种算法也是二分查找算法。

PS:不想看过程的,直接拉到万能公式,即看即用!

两种二分查找

第一种

在这里插入图片描述

第二种

在这里插入图片描述
在这里插入图片描述
从上面可以看出:

第二种方式有两种边界转移方式:

  • left = mid + 1;right = mid; 这种不会产生死循环
  • left = mid-1;right = mid; 这种会产生死循环(循环条件一直成立),此时需要把mid向上取整。

思维导图

在这里插入图片描述

万能公式

int search(int[] nums, int target, int l, int r) {
	while (l < r) {
		int mid = l + (r - l) / 2;
		
		// if (符合条件) return mid;

		if (target在右区间) {
			l = mid + 1;
		} else {
			r = mid;
		}
	}
	if (符合条件) return l;
	else return -1;
}

适用于:
在这里插入图片描述

主要注意的几个点:

  • while继续循环的条件:left<right; 注意不要带等于号,while退出循环的时候,一定是left==right,所以不需要额外的考虑用left还是right去判断条件。
  • 取中位数的时候,防止left和right过大,以至于left+right超过int类型上限,可以用mid = left + (right - left) / 2;来求中位数。
  • 在while中,如果题目说明了没有重复元素的话,那么万能公式中注释的if条件可以去掉注释,以便提前跳出条件,如果存在相同的元素让输出最左边或者最右边的元素,那么这个if条件就不能加
  • 赋值left和right边界的时候,需要注意left一定要先赋值,跟快排需要先从右边一样。

代码示例(套公式示例)

LeetCode704

【题目】

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。

【示例】

输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4

【代码】

public int search(int[] nums, int target) {
        if (nums==null || nums.length==0)
            return -1;

        int left,right,mid;

        left = 0;
        right = nums.length-1;

        while (left < right) {
            mid = left + (right - left) / 2;
            // if (nums[mid]==target)
            //     return mid;

            if (nums[mid]<target)
                left = mid+1;
            else
                right = mid;
        }

		// 由于条件可能不存在,所以需要额外判断
        if (target==nums[left])
            return left;
        else return -1;
    }

LeetCode35

【题目】

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

你可以假设数组中无重复元素。

【示例】

输入: [1,3,5,6], 5
输出: 2

输入: [1,3,5,6], 2
输出: 1

输入: [1,3,5,6], 0
输出: 0

输入: [1,3,5,6], 7
输出: 4

【代码】

public int searchInsert(int[] nums, int target) {
        if (nums==null || nums.length==0)
            return 0;

        int left,right,mid;
        left = 0;
        right = nums.length-1;

		// 最大值的情况需要额外判断
        if (target>nums[right]) return right+1;

        while (left < right) {
            mid = left + (right - left) / 2;
            // if (nums[mid]==target)
            //     return mid;

            if (nums[mid]<target)
                left = mid+1;
            else
                right = mid;
        }

		// 由于条件一定成立,所以直接输出即可
        return left;
    }

LeetCode1095

【题目】

给你一个 山脉数组 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() - 会返回该数组的长度

【示例】

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

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

【思考】

  • 先找到山顶元素 mountaintop 所在的索引。

  • 在前有序且升序数组中找 target 所在的索引,如果找到了,就返回,如果没有找到,就执行第 3 步;

  • 如果步骤 2 找不到,就在后有序且降序数组中找 target 所在的索引。

所以问题的关键就是如何在一个先升后降的数组中找到最高的一个。

	// 寻找峰顶的代码
	public int getTop(MountainArray mountainArr) {
        int left,right,mid,midVal,midAf;

        left = 0;
        right = mountainArr.length()-1;

        while (left < right) {
            mid = left + (right - left) / 2;

            midVal = mountainArr.get(mid);
            midAf = mountainArr.get(mid+1);

            if (midAf > midVal)// 如果当前的数比右边的数小,它一定不是山顶
                left = mid + 1;
            else
                right = mid;
        }
        return left;
    }

在这里插入图片描述

如果当前的数比右边的数小,它一定不是山顶,又由于left和right的二分是mid在左区域(看上图),所以最后跳出的时候,只有两个元素,mid在左边,left和right同时指向右边,右边就是那个峰顶。

【全部代码】

/**
 * // 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() {}
 * }
 */
 
class Solution {
    public int findInMountainArray(int target, MountainArray mountainArr) {
        if (mountainArr==null || mountainArr.length()==0)
            return -1;

        int l,top,leftIndex;
        top = getTop(mountainArr);
        l = 0;
        leftIndex = getAsc(mountainArr,target,l,top);
        if (leftIndex != -1)
            return leftIndex;
        else
            return getDesc(mountainArr,target,top,mountainArr.length()-1);

    }

    /**
     * 求得山峰的峰顶元素
     * */
    public int getTop(MountainArray mountainArr) {
        int left,right,mid,midVal,midAf;

        left = 0;
        right = mountainArr.length()-1;

        while (left < right) {
            mid = left + (right - left) / 2;

            midVal = mountainArr.get(mid);
            midAf = mountainArr.get(mid+1);

            if (midAf > midVal)
                left = mid + 1;
            else
                right = mid;
        }
        return left;
    }

    /**
     * 左边升序求指定元素
     * */
    public int getAsc(MountainArray mountainArr, int target, int l ,int r) {
        while (l < r) {
            int mid = l + (r - l) / 2;
            if (mountainArr.get(mid) < target)
                l = mid + 1;
            else r = mid;
        }

        if (mountainArr.get(l) == target)
            return l;
        else return -1;
    }

    /**
     * 右边降序求指定元素
     * */
    public int getDesc(MountainArray mountainArr, int target, int l ,int r) {
        while (l < r) {
            int mid = l + (r - l) / 2;
            if (mountainArr.get(mid) > target)
                l = mid + 1;
            else r = mid;
        }

        if (mountainArr.get(l) == target)
            return l;
        else return -1;
    }

}

// 用于本地测试,提交的时候可以不写以下代码
class MountainArray {
    ArrayList<Integer> table;
    public MountainArray() {
        table = new ArrayList();
    }
    public int get(int index) {
        return table.get(index);
    }
    public void add(int val) {
        table.add(val);
    }
    public void addAll(int[] a) {
        for (int i=0; i<a.length; i++) {
            table.add(a[i]);
        }
    }
    public int length() {
        return table.size();
    }
}

【Java 面试那点事】

这里致力于分享 Java 面试路上的各种知识,无论是技术还是经验,你需要的这里都有!

这里可以让你【快速了解 Java 相关知识】,并且【短时间在面试方面有跨越式提升】

面试路上,你不孤单!
在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章