二分查找

一、二分查找

二分查找针对的是一个有序的数据集合,查找思想有点类似分治思想。每次通过跟区间的中间元素对比,将待查找的区间缩小为之前的一半,直到找到要查找的元素,或者区间被缩小为0

二分查找的时间复杂度 O ( l o g n ) O(logn) O(logn)

非递归实现:

    public int bsearch(int[] nums, int value) {
        int low = 0;
        int high = nums.length - 1;
        while (low <= high) {
            int mid = low + ((high - low) >> 1);
            if (nums[mid] > value) {
                high = mid - 1;
            } else if (nums[mid] < value) {
                low = mid + 1;
            } else {
                return mid;
            }
        }
        return -1;
    }

递归实现:

    public int bsearch(int[] nums, int value) {
        return bsearchInternally(nums, 0, nums.length - 1, value);
    }

    private int bsearchInternally(int[] nums, int low, int high, int value) {
        if(low>high) return -1;
        int mid = low + ((high - low) >> 1);
        if (nums[mid] > value) {
            return bsearchInternally(nums, low, mid - 1, value);
        } else if (nums[mid] < value) {
            return bsearchInternally(nums, mid + 1, high, value);
        } else {
            return mid;
        }
    }

二、二分查找应用场景局限性

首先,二分查找依赖的是数组,主要原因是二分查找算法需要按照下标随机访问元素。数组按照下标随机访问数据的时间复杂度是 O ( 1 ) O(1) O(1),而如果使用链表,链表随机访问的时间复杂度是 O ( n ) O(n) O(n)。所以,如果数据使用链表存储,二分查找的时间复杂度就会变得很高

其次,二分查找针对的是有序数据

三、二分查找变种问题

1、查找第一个值等于给定值的元素

    public int bsearch(int[] nums, int value) {
        int low = 0;
        int high = nums.length - 1;
        while (low <= high) {
            int mid = low + ((high - low) >> 1);
            if (nums[mid] > value) {
                high = mid - 1;
            } else if (nums[mid] < value) {
                low = mid + 1;
            } else {
                if ((mid == 0) || nums[mid - 1] != value) return mid;
                else high = mid - 1;
            }
        }
        return -1;
    }

2、查找最后一个值等于给定值的元素

    public int bsearch(int[] nums, int value) {
        int low = 0;
        int high = nums.length - 1;
        while (low <= high) {
            int mid = low + ((high - low) >> 1);
            if (nums[mid] > value) {
                high = mid - 1;
            } else if (nums[mid] < value) {
                low = mid + 1;
            } else {
                if ((mid == 0) || nums[mid + 1] != value) return mid;
                else low = mid + 1;
            }
        }
        return -1;
    }

3、查找第一个大于等于给定值的元素

    public int bsearch(int[] nums, int value) {
        int low = 0;
        int high = nums.length - 1;
        while (low <= high) {
            int mid = low + ((high - low) >> 1);
            if (nums[mid] >= value) {
                if (mid == 0 || nums[mid - 1] < value) return mid;
                else high = mid - 1;
            } else {
                low = mid + 1;
            }
        }
        return -1;
    }

4、查找最后一个小于等于给定值的元素

    public int bsearch(int[] nums, int value) {
        int low = 0;
        int high = nums.length - 1;
        while (low <= high) {
            int mid = low + ((high - low) >> 1);
            if (nums[mid] <= value) {
                if (mid == 0 || nums[mid + 1] > value) return mid;
                else low = mid + 1;
            } else {
                high = mid - 1;
            }
        }
        return -1;
    }

四、二分查找相关题目

1、LeetCode69:x的平方根

实现int sqrt(int x)函数

计算并返回x的平方根,其中x是非负整数

由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去

示例 1:

输入: 4
输出: 2

示例 2:

输入: 8
输出: 2
说明: 8 的平方根是 2.82842...,由于返回类型是整数,小数部分将被舍去

题解:

    public int mySqrt(int x) {
        long low = 0;
        long high = x / 2 + 1;
        while (low < high) {
            //这里一定取右中位数,如果取左中位数,代码会进入死循环
            long mid = low + ((high - low + 1) >> 1);
            long result = mid * mid;
            if (result > x) {
                high = mid - 1;
            } else {
                low = mid;
            }
        }
        return (int) low;
    }

2、LeetCode367:有效的完全平方数

给定一个正整数num,编写一个函数,如果num是一个完全平方数,则返回True,否则返回False

说明:不要使用任何内置的库函数,如sqrt

示例 1:

输入:16
输出:True

示例 2:

输入:14
输出:False

题解:

    public boolean isPerfectSquare(int num) {
        long low = 0;
        long high = num / 2 + 1;
        while (low < high) {
            long mid = low + ((high - low + 1) >> 1);
            long result = mid * mid;
            if (result > num) {
                high = mid - 1;
            } else {
                low = mid;
            }
        }
        return low * low == num ? true : false;
    }

3、LeetCode153:寻找旋转排序数组中的最小值

假设按照升序排序的数组在预先未知的某个点上进行了旋转

(例如,数组[0,1,2,4,5,6,7]可能变为[4,5,6,7,0,1,2])

请找出其中最小的元素

你可以假设数组中不存在重复元素

示例 1:

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

示例 2:

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

题解:

    public int findMin(int[] nums) {
        int low = 0;
        int high = nums.length - 1;
        //nums.length==1时nums[high]==nums[0]
        if (nums[high] >= nums[0]) return nums[0];
        while (high >= low) {
            int mid = low + ((high - low) >> 1);
            //先判断nums[mid + 1] < nums[mid]再判断nums[mid - 1] > nums[mid],否则会出现数组索引越界,案例为[2,1]
            if (nums[mid + 1] < nums[mid]) {
                return nums[mid + 1];
            }
            if (nums[mid - 1] > nums[mid]) {
                return nums[mid];
            }
            if (nums[mid] > nums[0]) {
                low = mid + 1;
            } else {
                high = mid - 1;
            }
        }
        return -1;
    }

4、LeetCode33:搜索旋转排序数组

假设按照升序排序的数组在预先未知的某个点上进行了旋转

(例如,数组[0,1,2,4,5,6,7]可能变为[4,5,6,7,0,1,2])

搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回-1

你可以假设数组中不存在重复的元素

示例 1:

输入: nums = [4,5,6,7,0,1,2], target = 0
输出: 4

示例 2:

输入: nums = [4,5,6,7,0,1,2], target = 3
输出: -1

题解:

    public int search(int[] nums, int target) {
        int low = 0;
        int high = nums.length - 1;
        while (high >= low) {
            int mid = low + ((high - low) >> 1);
            if (nums[mid] == target) return mid;
            if (nums[low] <= nums[mid]) {
                if (target >= nums[low] && target < nums[mid]) high = mid - 1;
                else low = mid + 1;
            } else {
                if (target > nums[mid] && target <= nums[high]) low = mid + 1;
                else high = mid - 1;
            }
        }
        return -1;
    }

5、LeetCode74:搜索二维矩阵

编写一个高效的算法来判断m x n矩阵中,是否存在一个目标值。该矩阵具有如下特性:

  • 每行中的整数从左到右按升序排列
  • 每行的第一个整数大于前一行的最后一个整数

示例 1:

输入:
matrix = [
  [1,   3,  5,  7],
  [10, 11, 16, 20],
  [23, 30, 34, 50]
]
target = 3
输出: true

示例 2:

输入:
matrix = [
  [1,   3,  5,  7],
  [10, 11, 16, 20],
  [23, 30, 34, 50]
]
target = 13
输出: false

题解:

    public boolean searchMatrix(int[][] matrix, int target) {
        int m = matrix.length;
        if (m == 0) return false;
        int n = matrix[0].length;
        int left = 0, right = m * n - 1;
        int pivotIdx, pivotElement;
        while (left <= right) {
            pivotIdx = (left + right) / 2;
            pivotElement = matrix[pivotIdx / n][pivotIdx % n];
            if (target == pivotElement) return true;
            else {
                if (target < pivotElement) right = pivotIdx - 1;
                else left = pivotIdx + 1;
            }
        }
        return false;
    }

常用数据结构的时间、空间复杂度:

二分查找_第1张图片

https://www.bigocheatsheet.com/

你可能感兴趣的:(#,数据结构与算法,二分查找,二分查找变种)