二分法查找

108 阅读2分钟
总结
  • 如果只是查找一个数是否存在,那就是在循环中确定,最后找不到返回-1,对于这种题目,如果还包含重复的元素,也没问题,因为查找自带对 == 的处理

  • 如果题目有其他的需求,比如峰值函数和最小值这种,那么就是在最后返回,返回一个循环中得到的变量,对于这种题目,如果还包含重复元素,那么就需要分成三种情况, >、 =、 < 在一个有序数组中找某一个数是否存在,可以考虑二分的方法,时间复杂度是O(logN)

相关的代码是:

    public static void main(String[] args) {
        //一定要是有序的
        int[] nums = new int[]{1,2,3,5,8,9,11};
        System.out.println(binarySearch(nums,8));
    public static int binarySearch(int[] arr, int target){
        int low = 0; int high = arr.length - 1; int mid = 0;
        //核心代码,<=是为了保证都可以取到
        /注意这里有等于号
        while(low <= high){
            //这样写有一个问题,就是(low + high)可能会溢出
            //所以还是推荐mid = low + (high - low) / 2 或者 mid = low + (high - low) >> 2
            mid = (low + high)/2;
            if(arr[mid] == target){
                return mid;
            }else if(arr[mid] > target){
                high = mid -1;
            }else {
                low = mid + 1;
            }
        }
        return -1;
    }

相关的题目:

峰值元素是指其值大于左右相邻值的元素。给你一个输入数组 nums,找到峰值元素并返回其索引。数组可能包含多个峰值,在这种情况下,返回 任何一个峰值 所在位置即可。注意:对于所有有效的 i 都有 nums[i] != nums[i + 1]

注意可能变界限的时候左边和右边关于mid的表达式可能不同,如下面:

对应的代码是:

class Solution {
    public int findPeakElement(int[] nums) {
        int l = 0, r = nums.length - 1;
        //注意这里没有等于号
        while (l < r) {
            int mid = (l + r) / 2;
            if (nums[mid] > nums[mid + 1])
                //注意这里的右侧是mid不在减1,这和这个题利用升序有关系
                r = mid;
            else
                l = mid + 1;
        }
        return l;
    }
}

时间复杂度是O(logN),空间复杂度是O(1)。

已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后,得到输入数组。例如,原数组 nums = [0,1,4,4,5,6,7] 在变化后可能得到:

若旋转 4 次,则可以得到 [4,5,6,7,0,1,4]

若旋转 7 次,则可以得到 [0,1,4,4,5,6,7]

注意,数组 [a[0], a[1], a[2], ..., a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], ..., a[n-2]] 。

给你一个可能存在 重复 元素值的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的最小元素 。

链接:leetcode-cn.com/problems/fi…

class Solution {
    //体会二分法的妙用,自己在这一块不是很熟
    //整体思路:要画出来整体的数字规律,而不是找几个数组去试
    public int findMin(int[] nums) {
        int left = 0;
        int right = nums.length - 1;
        int pivot = 0;
        //一定要这样,否则可能会出现死循环
        while(left < right){
            pivot = left + (right - left) / 2;
            //大于的情况
            if(nums[pivot] > nums[right]){
                left = pivot + 1;
            //小于的情况
            }else if(nums[pivot] < nums[right]){
                right = pivot;
            //分出了等于的情况
            }else{
                right--;
            }
        }
        return nums[left];
    }
}

如果上面的题干中改成没有重复的元素,那么解法就不需要分成>、 =、 <三种情况了。

class Solution {
    public int findMin(int[] nums) {
        int low = 0;
        int high = nums.length - 1;
        while (low < high) {
            int pivot = low + (high - low) / 2;
            if (nums[pivot] < nums[high]) {
                high = pivot;
            //直接两种情况就OK,一个是小于,一个小于等于
            } else {
                low = pivot + 1;
            }
        }
        return nums[low];
    }
}