我要成为算法高手-二分查找篇

目录

  • 题目1:二分查找
    • 总结朴素二分模版
  • 题目2:在排序数组中查找元素的第一个和最后一个位置
    • 总结二分模版
  • 题目3:x的平方根
  • 题目4:搜索插入位置
  • 题目5:山脉数组的峰顶索引
  • 题目6:寻找峰值
  • 题目7:寻找旋转数组中的最小值
  • 题目8:点名

当数组具有二段性时,可以使用二分查找,二分查找是什么?二段性又是什么?请看题目1

题目1:二分查找

题目链接:704. 二分查找 - 力扣(LeetCode)
在这里插入图片描述

暴力解法:

从左往右依次遍历,如果找到返回结果,如果没找到返回-1

二分查找解法:利用数组有序的特点,对暴力解法进行优化,在数组中选取某个位置的值与目标值对比,对比之后能够划分出两个区间,一个区间是小于目标值的,另一个区间是大于目标值的,因此我们可以一口气筛选掉一部分的数据。那么选取哪个位置比较合适?根据概率学的理论,选取数组二分之一位置比较合适,时间复杂度最好

核心思路:

在这里插入图片描述

细节问题:循环何时结束?

当 left>right时,循环结束,left和right区间的数是没有判断的,所以当left和right同时指向一个数时(left=right),循环仍然进行。

二段性:根据规律在数组中选取某个位置之后能够把数组划分出两个区间,根据规律我们可以舍去掉一个区间,进而能在剩下的区间寻找结果。

代码实现:

class Solution {
    public int search(int[] nums, int target) {
        int left = 0, right = nums.length-1, ret = -1;
        while (left <= right) {
            // int mid = (left + right) / 2;
            int mid = left+ (right-left)/2;//防止溢出
            if (target > nums[mid]) {
                left = mid + 1;
            } else if (target < nums[mid]) {
                right = mid-1;
            } else {
                ret = mid;
                break;
            }
        }        
        return ret;
    }
}

总结朴素二分模版

while (left <= right) {
    //int mid = (left + right)/2;
    int mid = left + (right - left)/2;//防止溢出
    if (.....) {
        left = mid + 1;
    } else if (.....) {
        right = mid-1;
    } else {
        return .....;
    }
}

根据题目要求,根据二段性来填写(…)

还有一个版本

while (left <= right) {
    //int mid = (left + right + 1)/2;
    int mid = left + (right - left + 1)/2;//防止溢出
    if (.....) {
        left = mid + 1;
    } else if (.....) {
        right = mid-1;
    } else {
        return .....;
    }
}

上面两个区别是:如果数组元素个数是偶数,+1和不+1,mid位置不一样,但是对于朴素版本的模版来说,加不加1都无所谓

题目2:在排序数组中查找元素的第一个和最后一个位置

题目链接:34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)

在这里插入图片描述

暴力解法:

非常简单粗暴:

class Solution {
    public int[] searchRange(int[] nums, int target) {
        int[] ret = new int[]{-1,-1};
        if(nums==null){
            return ret;
        }
        for(int i=0;i<nums.length;i++) {
            if(nums[i]==target) {
                ret[0]=i;
                break;
            }
        }
        for(int i=nums.length-1;i>=0;i--) {
            if(nums[i]==target) {
                ret[1]=i;
                break;
            }
        }
        return ret;
    }
}

二分查找思路如图

在这里插入图片描述

代码实现:

class Solution {
    public int[] searchRange(int[] nums, int target) {
        int[] ret = new int[2];
        ret[0] = -1;
        ret[1] = -1;
        if(nums.length==0){
            return ret;
        }
        //求左端点
        int left = 0,right=nums.length-1;
        while(left<right){
            int mid = left+(right-left)/2;
            if(nums[mid]<target){
                left = mid+1;
            } else {
                right = mid;
            }
        }
        //此时,left=right
        //判断
        if(nums[left]==target){
            ret[0] = left;
        } else {
            //没找到
            return ret;
        }
        //求右端点
        left = 0;
        right = nums.length -1;
        while(left<right){
            int mid = left+(right-left+1)/2;
            if(nums[mid]>target){
                right = mid-1;
            } else{
                left = mid;
            }
        }
        //此时,left=right
        if(nums[right]==target){
            ret[1] = right;
        } else {
            return ret;
        }
        return ret;
    }
}

总结二分模版

1、

while(left < right){
    int mid = left + (right - left) / 2;
    if(......) {
        left = mid +1;
    } else {
        right = mid;
    }
}

2、

while(left < right){
    int mid = left + (right - left + 1) / 2;
    if(......) {
        left = mid;
    } else {
        right = mid -1;
    }
}

根据题目的要求,判断left和right,接着来确定求mid时是否需要+1

题目3:x的平方根

69. x 的平方根 - 力扣(LeetCode)

在这里插入图片描述

解题思路如图:
在这里插入图片描述

根据最终返回的结果,可以将划分为2个部分,一部分是平方值小于等于x,另一部分则是大于x,如果mid落在左边部分,left = mid;如果mid落在右边部分,right = mid-1,接下来我们就直接套模版就好了

class Solution {
    public int mySqrt(int x) {
        long left = 0,right = x;
        while(left<right){
            long mid = left + (right - left+1)/2;
            if(mid*mid<=x){
                left = mid;
            } else {
                right = mid-1;
            }
        }
        return (int)left;
    }
}

题目4:搜索插入位置

35. 搜索插入位置 - 力扣(LeetCode)

在这里插入图片描述

解题思路:二段性如图,找出二段性后,根据left和right的变化直接套模版,但是需要处理一下实例3的情况,如果target都大于数组中最后一个元素,直接返回数组长度即可
在这里插入图片描述

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

题目5:山脉数组的峰顶索引

852. 山脉数组的峰顶索引 - 力扣(LeetCode)
在这里插入图片描述

解题思路:
在这里插入图片描述

代码实现:

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

题目6:寻找峰值

162. 寻找峰值 - 力扣(LeetCode)

在这里插入图片描述

二分查找:二段性如图所示

在这里插入图片描述

代码实现:

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

题目7:寻找旋转数组中的最小值

153. 寻找旋转排序数组中的最小值

在这里插入图片描述

解题思路:

题目要求的就是数组中的最小值

在这里插入图片描述

代码实现:

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

题目8:点名

LCR 173. 点名 - 力扣(LeetCode)

在这里插入图片描述

解题思路:

在这里插入图片描述

代码实现:

class Solution {
    public int takeAttendance(int[] records) {
        if (records.length == 1) {
            if (records[0] == 0) {
                return 1;
            }
            return records[0] - 1;
        }
        int left = 0;
        int right = records.length - 1;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (mid == records[mid]) {
                left = mid + 1;
            } else {
                right = mid;
            }
        }
        if (records[left] == left) {
            return left + 1;
        }
        return left;
    }
}
上一篇:开源音乐分离器Audio Decomposition:可实现盲源音频分离,无需外部乐器分离库,从头开始制作。将音乐转换为五线谱的程序


下一篇:[C/C++] move示例