算法-二分搜索

文章介绍了二分搜索的基本原理、常见题型如查找元素位置、搜索范围、山峰数组的峰顶索引以及寻找峰值元素,所有算法均保持了O(logn)的时间复杂度。
摘要由CSDN通过智能技术生成

1.绪论

1.1 二分搜索的特点

二分搜索主要是在升序数组之间,查询到合适的数字。二分查找要找到循环不变量,就是保持循环条件的语义不变。
二分查找的模板如下:
在这里插入图片描述

1.2 二分查找的题型汇总

  1. 查找升序数组中的某一个元素第一次出现的位置;
  2. 查询升序中的某个元素的所有值;
  3. 查询升序数组中某个元素出现的次数,就是2的变种;
  4. 查询某个元素在升序数组中插入的位置,1的变种;
  5. 遍历升序矩阵,即矩阵的行是升序的。这种相当于多个二分查找。时间复杂度是O(mlog(n));
  6. 寻找山峰数组的最大值(最小值)坐标,什么是山峰数组,左侧递增,右侧递减的数组可以称为山峰数组;

1.3 举例

704. 二分查找

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

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

输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1

题解:
比如这里:循环的语义就是从[left,right]之间寻找到target的元素,一定要保持闭区间。

class Solution {
    public int search(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        // 在[left,right]之间寻找target元素
        while (left <= right) {
            int mid = (left + right) / 2;
            if (nums[mid] == target) {
                return mid;
            } else if (nums[mid] < target) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        return -1;
    }
}

34. 在排序数组中查找元素的第一个和最后一个位置

题目描述

给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]。

你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。

示例 1:

输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]
示例 2:

输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]
示例 3:

输入:nums = [], target = 0
输出:[-1,-1]

题解
该题可以理解为利用二分查找寻找第一次出现元素的位置和最后一次出现元素的位置.

class Solution {
    public int[] searchRange(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        int lr = -1;
        int rr = -1;
        // [left,right]寻找target的数组
        // [left,right]寻找第一个等于target的元素
        while (left <= right) {
            int mid = (left + right) / 2;
            if (nums[mid] == target) {
                lr = mid;
                right = mid - 1;
            } else if (nums[mid] < target) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }

         // [left,right]寻找最后一个等于target的元素
        left = 0;
        right = nums.length - 1;
        while (left <= right) {
            int mid = (left + right) / 2;
            if (nums[mid] == target) {
                rr = mid;
                left = mid + 1;
            } else if (nums[mid] < target) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        return new int[] { lr, rr };
    }
}

240. 搜索二维矩阵 II

题目描述

编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性:

每行的元素从左到右升序排列。
每列的元素从上到下升序排列。
在这里插入图片描述
输入: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

题解:
方式1:二分查找
即通过二分查找遍历行这么多次。

class Solution {
    public boolean searchMatrix(int[][] matrix, int target) {
        for (int i = 0; i < matrix.length; i++) {
            if (search(matrix[i], target)) {
                return true;
            }
        }
        return false;
    }

    private boolean search(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        while (left <= right) {
            int mid = (left + right) / 2;
            if (target == nums[mid]) {
                return true;
            } else if (nums[mid] < target) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        return false;
    }
}

方式2:z形遍历

在这里插入图片描述
通过上面的图形可以看出,对于5这个元素,右下角全是比它大的元素,左上角全是比它小的元素。所以我们可以从右上角开始遍历,分为3种情况:
1.num[x][y] == target ,当前元素就是目标值;
2.num[x][y] < target, 证明当前元素左上角的元素需要排除,所以横线需要向下移动,x++;
3.nums[x][y] > target,这么当前元素右下角元素需要排除,所以竖线需要向左移动,y–.

class Solution {
    public boolean searchMatrix(int[][] matrix, int target) {
        int x = 0;
        int y = matrix[0].length - 1;
        while (x <= (matrix.length - 1) && y >= 0) {
            if (matrix[x][y] == target) {
                return true;
            } else if (matrix[x][y] < target) {
                x++;
            } else {
                y--;
            }
        }
        return false;
    }
}

备注:所以对于升序的矩形的搜索,也可以考虑这种情况

LCR 069. 山脉数组的峰顶索引

題目描述

符合下列属性的数组 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 ,即山峰顶部。

示例 1:

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

输入:arr = [1,3,5,4,2]
输出:2
示例 3:

输入:arr = [0,10,5,2]
输出:1
示例 4:

输入:arr = [3,4,5,1]
输出:2
示例 5:

输入:arr = [24,69,100,99,79,78,67,36,26,19]
输出:2

提示:

3 <= arr.length <= 104
0 <= arr[i] <= 106
题目数据保证 arr 是一个山脉数组

题解:
由于题目保证数组arr一定是一个山脉数组,并且元素个数一定大于3个,所以可以从[1,arr,.length-2]数组中寻找到元素。这样能够保证下标不会越界。

class Solution {
    public int peakIndexInMountainArray(int[] arr) {
        int left = 0;
        int right = arr.lenght - 1;
        //在[left,right]中寻找满足山脉数组的值
        while (left <= right) {
            int mid = (left + right)/2;
            if(arr[mid] > arr[mid - 1] && arr[mid] < arr[mid + 1]) {
                return mid;
            }
            if(arr[mid] > arr[mid - 1]) {
                //向右寻找
                left = mid + 1
            }else {
                right = mid - 1;
            }
        }
    }
}

162. 寻找峰值

题目描述

峰值元素是指其值严格大于左右相邻值的元素。

给你一个整数数组 nums,找到峰值元素并返回其索引。数组可能包含多个峰值,在这种情况下,返回 任何一个峰值 所在位置即可。

你可以假设 nums[-1] = nums[n] = -∞ 。

你必须实现时间复杂度为 O(log n) 的算法来解决此问题。

示例 1:

输入:nums = [1,2,3,1]
输出:2
解释:3 是峰值元素,你的函数应该返回其索引 2。
示例 2:

输入:nums = [1,2,1,3,5,6,4]
输出:1 或 5
解释:你的函数可以返回索引 1,其峰值元素为 2;或者返回索引 5, 其峰值元素为 6。

题解:
标准二分查找模板套用,由于我采用的模板循环不变量是在[left,right]中寻找target元素,但是此题会根据左侧或者右侧的元素都为负无穷,nums[0]和nums[length-1]这两个边界值需要特殊处理

class Solution {
    public int findPeakElement(int[] nums) {
        if (nums.length == 1) {
            return 0;
        }
        int left = 0;
        int right = nums.length - 1;
        while (left <= right) {
            int mid = (left + right) /2;
            if (mid == 0) {
                if (nums[mid] > nums[mid + 1]) {
                    return 0;
                } else {
                    return 1;
                }
            } else if (mid == nums.length - 1) {
                if (nums[mid] > nums[mid - 1]) {
                    return nums.length - 1;
                } else {
                    return nums.length - 2;
                }
            } else if (nums[mid] > nums[mid - 1] && nums[mid] > nums[mid + 1]) {
                return mid;
            } else if (nums[mid] < nums[mid + 1]) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        return -1;
    }
}

-------to be continue ------------------

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值