二分查找算法

214 阅读4分钟

1.二分查找

今天重温了一下二分查找
二分查找有两种写法,区间左闭右闭[x,x](本文介绍)和区间左闭右开[x,x)(项目中很少使用左开右闭(x,x]这种),二者的不同点在于获取right时是right=nums.size()还是right=nums.size()-1,左闭右开详细请看代码随想录,左闭右开区间因为要保持区间左闭右开的状态在更新right时是right = middle,不是middle - 1,因为他的右端点不是闭区间,这个把right 更新为 middle,在下一轮比较时middle这个数也用不着,因为是开区间,右端点只是个代表,取不到。while的判断条件为left < right ,没有等于号,因为在[left , right) 区间中 legt == right 没有意义。 学习地址

package array;

public class TestBinarySearch {

    public static void main(String[] args) {

        int[] nums = {-1, 0,3,5,9,12};
        int target = -2;
        int binarySearch = binarySearch(nums, target);
        System.out.println(binarySearch);

    }

    public static int binarySearch(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        // 避免当 target 小于nums[0] nums[nums.length - 1]时多次循环运算
        if (target < nums[0] || target > nums[nums.length - 1]) {
            return -1;
        }
        while (left <= right) {
            int middle = (left + right) / 2;
            if (target == nums[middle]) {
                return middle;//返回数组下标
            }
            // 目标值大于数组中间的值 则将数组左半部分舍弃 更新左指针 left = middle + 1
            else if (target > nums[middle]) {
                left = middle + 1;
            }

            // 目标值小于数组中间的值 则将数组右半部分舍弃 更新右指针 right = middle - 1
            else if (target < nums[middle]) {
                right = middle - 1 ;
            }
        }
        return -1;

    }
}

代码中有一部分容易忘记 忘了可就一直循环下去了

 // 避免当 target 小于nums[0] nums[nums.length - 1]时多次循环运算
        if (target < nums[0] || target > nums[nums.length - 1]) {
            return -1;
        }

写个小总结:

  1. 排序好的数组,目标值target
  2. left 和 right 指针
  3. 比较目标值和array[middle] (middle = (left + right) / 2)
  4. 更新指针, 注意left = middle + 1,right = middle -1; 加一减一是因为被比较的array[middle] 在下一轮不必再出现了,毕竟它!=target

上面属于是二分查找的基础,现在看一个稍有难度题力扣题目链接

描述:给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。

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

进阶:你可以设计并实现时间复杂度为 O(logn)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]

我的题解:

package array.binarySearch;

import java.util.Arrays;

/**
 * 小记对二分查找的理解:
 * 1.使用二分查找条件 : 升序排列的数组   目标值target
 * 2.二分查找的目的是查找,那么问题来了,是查找一个呢(该数组中就一个值等于target),还是查找一个范围呢(该数组中有多个值等于target)。
 *   查找一个值(找到直接return这个值的下标就行了)。
 *
 *   查找一个范围(数组中有target的一个片段序列,问题转化为找到这个片段序列左端点和右端点,
 *      如:{5,7,7,8,8,8,8,8,8,8,10},片段序列是[8,8,8,8,8,8,8]
 *      怎么找这两个端点值呢? 二分查找中的left和right这两个端点在每次比较后都会进行更新。注意while(left <= right)这个判断条件,退出循环时(left > right)
 *      当target <= nums[middle]时,"<" 成立舍弃右半部分,更新right,"=="成立,只更新right,最后一次时right < left,出循环,此时的left更新到了片段序列的最后一个位置的下一个位置,
 *      这就能找到右端点了。
 *      当target >= nums[middle], ">" 舍弃左半部分,更新left,"=="成立,只更新,更新left最后一次时left > right,出循环,此时的right更新到了片段序列的第一个位置的前一个位值,
 *      这就找到了左端点。)
 * 3.注意查找一个值和一个范围的区别,查找一个值while循环中if 判断分三种情况。在查找一个范围时while循环中的if 判断分两种情况。
 * 4.上面的看不懂,或忘了,找个例子画画图,主要看left,right,middle这三个指针,调试调试,就明白了。
 */
public class LeetCode34v3 {
    public static void main(String[] args) {
        LeetCode34v3 object = new LeetCode34v3();
//        int[] nums =  {5,7,7,8,8,10};
//        int target = 6;
        int[] nums = {1};
        int target = 1;
        int[] arr = object.searchRange(nums, target);
        System.out.println(Arrays.toString(arr));
    }
    /*
    写题目前先想好思路:本题分析一下几种情况
    情况一:target 在数组范围的右边或者左边,例如数组{3, 4, 5},target为2或者数组{3, 4, 5},target为6,此时应该返回{-1, -1}
    情况二:target 在数组范围中,且数组中不存在target,例如数组{3,6,7},target为5,此时应该返回{-1, -1}
    情况三:target 在数组范围中,且数组中存在target,例如数组{3,6,7},target为6,此时应该返回{1, 1}
     */
    public int[] searchRange(int[] nums, int target) {
        int leftBorder = getLeftBorder(nums, target);
        int rightBorder = getRightBorder(nums, target);
        //情况一,target不在这个范围中,边界值总有一个不会变
        if (leftBorder == -2 || rightBorder == -2){//leftBorder 和 rightBorder 初始值不要赋值-1,因为在int[] nums = {1};int target = 1;这种情况下rightBorder = -1
            return new int[]{-1,-1};
        }
        //情况三
        if (rightBorder - leftBorder > 1){//这里大于1,意思是数组中至少有一个target。不能是大于0,如2-1>0,但是在下标为1和2之间没有一个数,会return一个错误的值,第一次提交就是这样错的
            return new int[]{leftBorder + 1,rightBorder - 1};
        }
        //情况二
        return new int[]{-1,-1};
    }
    //找他的左边界
    int getLeftBorder(int[] nums,int target){
        int left = 0;
        int right = nums.length -1;
        int leftBorder = -2;//用-1代表边界的初始值
        while (left <= right){
            int middle = (left + right) / 2;
            if (target <= nums[middle]){
                //更新right,right的最终值就是leftBorder
                right = middle - 1;
                leftBorder = right;
            }else {
                //nums[middle] < target 更新left
                left = middle + 1;
            }
        }
        return leftBorder;
    }
    //找他的右边界
    int getRightBorder(int[] nums,int target){
        int left = 0;
        int right = nums.length - 1;
        int rightBorder = -2;
        while (left <= right){
            int middle = (left + right) / 2;
            if (target >= nums[middle]){
                left = middle + 1;
                rightBorder = left;
            }else {
                right = middle - 1;
            }
        }
        return rightBorder;
    }
}