4.二分查找

单调一维数组的二分查找及其变种:
一.二分查找
1.题目描述:
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。

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

3.解:

class Solution {
    public int search(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;

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

4.总结
变种(以下变种均适用于数组中存在重复值的情况):
以"1,2,3,3,4,5"为例,记住下面这些变种
(1)模板

class Solution {
    public int search(int[] array, int key) {
        int left = 0;
        int right = array.length - 1;

        while (left <= right) {
            int mid = left + (right-left) / 2;
            if (array[mid] ? key) {

            }
            else {

            }
        }
        return xxx;
    }
}

(2)关于返回left和right。
如果查找第一个XXX,返回left
如果查找最后一个XXX,返回right

(3)关于last和first以及<,>,=问题
"="
=last 查找最后一个等于key的元素 if: array[mid] <= key else: array[mid] > key return: right
first= 查找第一个等于key的元素 if: array[mid] >= key else:array[mid] < key return: left

"<" ">":
<last 查找最后一个小于key的元素 if: array[mid] < key else: array[mid] >= key return: right
first> 查找第一个大于key的元素 if: array[mid] >= key else:array[mid] < key return: left

"<=" ">="
与"="的情况完全一样,只不过"="中需要在return之前加判断语句

=last

    if (right >= 0 && array[right] == key) {
        return right;
    }
    return -1;

first=

    if (left < array.length && array[left] == key) {
        return left;
    }
    return -1;

单调一维数组旋转后的二分查找及其变种:
https://leetcode-cn.com/problems/search-rotate-array-lcci/solution/xuan-zhuan-shu-zu-cong-yi-dao-nan-ge-ge-dcv7a/
一.搜索旋转数组
1.题目描述:
搜索旋转数组。给定一个排序后的数组,包含n个整数,但这个数组已被旋转过很多次了,次数不详。请编写代码找出数组中的某个元素,假设数组元素原先是按升序排列的。若有多个相同元素,返回索引值最小的一个。

2.示例:
输入: arr = [15, 16, 19, 20, 25, 1, 3, 4, 5, 7, 10, 14], target = 5
输出: 8(元素5在该数组中的索引)

3.解:

class Solution {
    public int search(int[] arr, int target) {
        if (arr == null || arr.length == 0) return -1;
        int l = 0;
        int r = arr.length - 1;
        int mid;
        while (l < r) {
            mid = l + (r + 1 - l) / 2;
            if (arr[mid] < arr[r]) r = mid;
            else l = mid + 1;
        }
        r = l + arr.length - 1;
        if(target<arr[l]) return -1;
        while (l < r) {
            mid = l + (r + 1 - l) / 2;
            if (arr[mid % (arr.length - 1)] == target) return mid % (arr.length - 1);
            else if (arr[mid %  (arr.length - 1)] < target) l = mid + 1;
            else if (arr[mid %  (arr.length - 1)] > target) r = mid - 1;
        }
        return -1;
    }
}

4.总结

二维数组的二分查找及其变种:
一.统计有序矩阵中的负数
1.题目描述:
给你一个 m * n 的矩阵 grid,矩阵中的元素无论是按行还是按列,都以非递增顺序排列。
请你统计并返回 grid 中 负数 的数目。

2.示例:
输入:grid = [[4,3,2,-1],[3,2,1,-1],[1,1,-1,-2],[-1,-1,-2,-3]]
输出:8
解释:矩阵***有 8 个负数。

3.解:
(1)网友答案:

class Solution {
    public int countNegatives(int[][] grid) {
        int num = 0;
        int rowNum = grid.length;
        int colNum = grid[0].length;
        int leftSite =0;
        int rightSite = colNum-1;
        int midSite;


        for(int i=0;i<rowNum;i++){
            leftSite = 0;//重置左坐标为0,右坐标位置重置操作在后面

            //找到当前行的第一个不是负数的元素的位置
            while(leftSite<rightSite){
                midSite = (leftSite+rightSite+1)/2;
                if(grid[i][midSite]>=0) leftSite = midSite;
                else if(grid[i][midSite]<0) rightSite = midSite-1;
            }

            //改变下一行右坐标的值
            if(grid[i][leftSite]>=0){
                num +=colNum-(leftSite+1);
                rightSite = leftSite;
            }

            //改变下一行右坐标的值
            else {
                num +=(colNum-leftSite);
                rightSite = leftSite-1;
            }   
        }
        return num;
    }
}

二.二维数组中的查找
1.题目描述:
在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个高效的函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

2.示例:
现有矩阵 matrix 如下:

[
[1, 4, 7, 11, 15],
[2, 5, 8, 12, 19],
[3, 6, 9, 16, 22],
[10, 13, 14, 17, 24],
[18, 21, 23, 26, 30]
]
给定 target = 5,返回 true。

给定 target = 20,返回 false。

3.解:

(1)我的答案:
class Solution {
int rowNum;
int colNum;
int right;
int left;
int mid;

public boolean findNumberIn2DArray(int[][] matrix, int target) {
    rowNum = matrix.length;
    if (rowNum == 0) return false;
    colNum = matrix[0].length;

    for (int i = 0; i < rowNum; i++) {
        left = 0;
        right = colNum;
        while (left < right) {
            mid = (right + left) / 2;
            if (matrix[i][mid] == target) return true;
            else if (matrix[i][mid] < target) left = mid + 1;
            else if (matrix[i][mid] > target) right = mid;
        }
    }
    return false;
}
}

(2)网友答案:

class Solution {
    public boolean findNumberIn2DArray(int[][] matrix, int target) {
        if(matrix == null || matrix.length == 0) {
            return false;
        }
        int m = matrix.length, n = matrix[0].length;
        int row = 0, col = n - 1;
        while(row < m && col >= 0) {
            if(matrix[row][col] > target) {
                col--;
            }else if(matrix[row][col] < target) {
                row++;
            }else {
                return true;
            }
        }
        return false;
    }
}

4.总结
网友的答案更好,时间复杂度仅仅为logn,而我的答案多少有点儿暴力了,时间复杂度为nlogn。网友的答案的原理时从二维数组的右上角出发,大于target则左移一位,小于target则下移一位。

其他无类型的二分查找及其变种:

一.魔术索引
1.题目描述:
在数组A[0...n-1]中,有所谓的魔术索引,满足条件A[i] = i。给定一个单调非递减整数数组,编写一种方法找出魔术索引,若有的话,在数组A中找出一个魔术索引,如果没有,则返回-1。若有多个魔术索引,返回索引值最小的一个。

2.示例:
输入:nums = [0, 2, 3, 4, 5]
输出:0
说明: 0下标的元素为0

3.解:
(1)我的答案:

class Solution {
    public int findMagicIndex(int[] nums) {
        int index = -1;
        int right = nums.length - 1;
        int left = 0;
        int mid;

        while (left <= right) {
            mid = (right + left + 1) / 2;
            if (mid < nums[mid]) right = mid - 1;
            else if (nums[mid] < mid) left = mid + 1;
            else if (mid == nums[mid]) {
                index = mid;
                right = mid - 1;
            }
        }
        return index;
    }
}

(2)网友答案

class Solution {
    public int findMagicIndex(int[] nums) {
        for(int i=0;i<nums.length;){
            if(i==nums[i]) return i;
            i=Math.max(i+1,nums[i]);
        }     
        return -1;
    }
}

4.总结:这个题其实是一道大题的第二小问,第一小问是严格单调递增的整数数组,第二小问就是本题,改成了不严格的递增。
在严格单调递增的情况下,我的答案是可以的,即二分查找,此时的时间复杂度时O(logn),而第二问的目的是为了证明第二问不存在时间复杂度是O(logn)的解法,时间复杂度最小也得是O(n)。第二问采用常规的顺序遍历即可,当然也可以向网友那样采用跳跃法,都是可以的。

二.山脉数组的峰顶索引
1.题目描述:
符合下列属性的数组 arr 称为 山脉数组 :
arr.length >= 3
存在 i(0 < i < arr.length - 1)使得:
arr[0] < arr[1] < ... arr[i-1] < arr[i]
arr[i] > arr[i+1] > ... > arr[arr.length - 1]
给你由整数组成的山脉数组 arr ,返回任何满足 arr[0] < arr[1] < ... arr[i - 1] < arr[i] > arr[i + 1] > ... > arr[arr.length - 1] 的下标 i 。

2.示例:
输入:arr = [0,1,0]
输出:1

3.解:
(1)网友答案:

class Solution {
    public int peakIndexInMountainArray(int[] arr) {
        int l=0;
        int r=arr.length-1;
        int mid;
        while(l<r){
            mid = (l+r)/2;
            if(arr[mid]>arr[mid+1]) r=mid;
            else if(arr[mid]<arr[mid+1]) l=mid+1;
        }
        return l;
    }
}
class Solution {
    public int peakIndexInMountainArray(int[] arr) {
        for(int i =1;i<arr.length-1;i++){
            if(arr[i+1]<arr[i]) return i;
        }
        return -1;
    }
}

4.总结:这道题用二分查找时间复杂比for循环遍历的时间复杂度小。

全部评论

相关推荐

11-03 18:50
门头沟学院 Java
迷茫的大四🐶:问就是马上到,一周五天,6个月以上,全国可飞
点赞 评论 收藏
分享
评论
点赞
1
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务