刷题笔记之二分查找

前言

本博客是对二分查找做相关练习时所做笔记,主要内容来源二分查找
文中介绍了二分查找的三个模板以及相应题目的题解,后续会增加一些二分查找相关题目的练习。

基本介绍

  • 二分查找描述了在有序集合中搜索特定值的过程。
  • 使用的术语:
    • 目标 target —— 要查找的值
    • 索引 Index —— 要查找的当前位置
    • 左、右指示符 leftright —— 我们用来维持查找空间的指标
    • 中间指示符 mid —— 用来应用条件来确定我们应该向左查找还是向右查找的索引
  • 二分查找的过程:
    1. 预处理 —— 如果集合未排序,则进行排序。
    2. 二分查找 —— 使用循环或递归在每次比较后将查找空间划分为两半。
    3. 后处理 —— 在剩余空间中确定可行的候选者。

二分查找的三个模板

模板一

  • 代码:

    int binarySearch(int[] nums, int target){
        if(nums == null || nums.length == 0)
            return -1;
    
        int left = 0, right = nums.length - 1;
        while(left <= right){
            // 此形式可防止 (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; 
            }
        }
    
        // End Condition: left > right
        return -1;
    }
    
  • 说明:

    这是最基本的二分查找形式。

    • 初始条件:left = 0, right = length-1
    • 终止:left > right
    • 向左查找:right = mid-1
    • 向右查找:left = mid+1
33.搜索旋转排序数组
  • 题目描述:给你一个整数数组 nums ,和一个整数 target 。

    该整数数组原本是按升序排列,但输入时在预先未知的某个点上进行了旋转。(例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。

    请你在数组中搜索 target ,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。

  • 示例

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

    直接搜索过于简单,主要是二分搜索。

    二分搜索的关键在于确定要搜索的元素在左半部还是右半部。

    由于旋转后nums[0]>nums[nums.length-1],故可利用这一点首先确定nums[mid]在旋转后的左半段还是在右半段。

    如果在左半段,nums[mid]左边的部分肯定是有序的,若nums[left]<=target<nums[mid],则在[left,mid-1]中搜索,否则在[mid+1,right]中搜索;

    如果在右半段,nums[mid]右边的部分肯定是有序的,若nums[mid]<target<=nums[right],则在[mid+1,right]中搜索,否则在[left,mid-1]中搜索。

    这样,即完成了二分。

  • 代码:

    class Solution {
        public int search(int[] nums, int target) {
            int n = nums.length;
            int left = 0, right = n-1;
            while(left <= right) {
                int mid = left + (right-left)/2;
                if(nums[mid] == target) {
                    return mid;
                }
                //判断target在左边还是右边
                //首先判断nums[mid]在旋转的左半段还是右半段
                if(nums[mid] >= nums[0]) {
                    //nums[mid]在左半段
                    if(target>=nums[left] && target<nums[mid]) {
                        //target在左边
                        right = mid - 1;
                    }else {
                        //target在右边
                        left = mid + 1;
                    }
                }else {
                    //nums[mid]在右半段
                    if(target<=nums[right] && target>nums[mid]) {
                        left = mid + 1;
                    }else {
                        right = mid - 1;
                    }
                }
            }
            return -1;
        }
    }
    

模板二

  • 代码:

    int binarySearch(int[] nums, int target){
        if(nums == null || nums.length == 0)
            return -1;
    
        int left = 0, right = nums.length;
        while(left < right){
            // Prevent (left + right) overflow
            int mid = left + (right - left) / 2;
            if(nums[mid] == target){ return mid; }
            else if(nums[mid] < target) { left = mid + 1; }
            else { right = mid; }
        }
        
        return -1;
    }
    
  • 说明:适用于查找第一个满足条件的元素的情况,或者要查找的元素一定在数组中,通过不断缩小范围定位到那个元素

    • 初始条件:left = 0, right = length
    • 终止:left == right
    • 向左查找:right = mid
    • 向右查找:left = mid+1

    比如用于查找第一个大于等于target的数。

    int left = 0, right = nums.length;
    while(left < right){
        // 防止 (left + right) 溢出
        int mid = left + (right - left) / 2;
        if(nums[mid] < target) { left = mid + 1; }
        else { right = mid; }
    }
    return left;
    
162.寻找峰值
  • 题目描述:峰值元素是指其值大于左右相邻值的元素。

    给定一个输入数组 nums,其中 nums[i] ≠ nums[i+1],找到峰值元素并返回其索引。

    数组可能包含多个峰值,在这种情况下,返回任何一个峰值所在位置即可。

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

  • 示例:

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

    判断一个数是否为峰值,由于nums[-1] = nums[n] = -∞,若从头开始遍历,只需要检查它是否大于下一个元素 nums[i+1] 即可判断 nums[i]是否是峰值,也就是说第一个满足nums[i]>nums[i+1]的数nums[i]必为峰值。

    利用此特性,可以进行二分查找。

    若nums[mid]>nums[mid+1],则左半部(left,mid)必有峰值;

    若nums[mid]<nums[mid+1],则右半部(mid+1,right)必有峰值;

  • 代码:

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

模板三

  • 代码:

    int binarySearch(int[] nums, int target) {
        if (nums == null || nums.length == 0)
            return -1;
    
        int left = 0, right = nums.length - 1;
        while (left + 1 < right){
            // Prevent (left + right) overflow
            int mid = left + (right - left) / 2;
            if (nums[mid] == target) {
                return mid;
            } else if (nums[mid] < target) {
                left = mid;
            } else {
                right = mid;
            }
        }
    
        // Post-processing:
        // End Condition: left + 1 == right
        if(nums[left] == target) return left;
        if(nums[right] == target) return right;
        return -1;
    }
    
  • 说明:多用于要查找的值需要比较左右的场景,比如从有序数组中找到距离x最近的值

    可用于搜索需要访问当前索引及其直接左右邻居索引的时候。

    • 初始条件:left = 0, right = length-1
    • 终止:left + 1 == right
    • 向左查找:right = mid
    • 向右查找:left = mid
658.找到K个最接近的元素
  • 题目描述:给定一个排序好的数组 arr ,两个整数 kx ,从数组中找到最靠近 x(两数之差最小)的 k 个数。返回的结果必须要是按升序排好的。

    整数 a 比整数 b 更接近 x 需要满足:

    • |a - x| < |b - x| 或者
    • |a - x| == |b - x|a < b
  • 示例:

    输入:arr = [1,2,3,4,5], k = 4, x = 3
    输出:[1,2,3,4]
    
  • 分析:

    可先用二分查找找到最接近x的位置key,然后以key为中心,找到最靠近 xk 个数。

    二分查找可利用模板三,最终将位置锁定在left和right之间,再判断二者谁最接近x

  • 代码:

    class Solution {
        public List<Integer> findClosestElements(int[] arr, int k, int x) {
            List<Integer> ans = new ArrayList<>();
            //以距离x最近的值为起点,向两端寻找距离x最近的k个值
            int left = binarySearch(arr,x);
            int right = left;
    
            for(int i=1; i<k; i++) {
                //左边已到达端点
                if(left == 0) right++;
                //右边已到达端点
                else if(right == arr.length-1) left--;
                else if(x-arr[left-1] <= arr[right+1]-x){
                    //左边离的较近
                    left--;
                } else {
                    //右边离的较近
                    right++;
                }
            }
    
            for(int i=left; i<=right; i++) {
                ans.add(arr[i]);
            }
    
            return ans;
        }
    
        //二分查找找到距离x最近的值
        public int binarySearch(int[] arr, int x) {
            int left = 0;
            int right = arr.length - 1;
            //循环终止时距离x最近的值在left与right之间
            while(left < right-1) {
                int mid = left + (right - left) / 2;
                if(arr[mid] == x) return mid;
                else if(arr[mid] < x) {
                    //距离x最近的值在区间[mid,right]中
                    left = mid;
                } else {
                    //距离x最近的值在区间[left,mid]中
                    right = mid;
                }
            } 
    
            if(x-arr[left] <= arr[right]-x) return left;
            else return right;
        }
    }
    

题目练习

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值