二分法查找

本文深入解析二分查找算法,提供避免整型溢出的代码模板,对比两种循环退出条件,介绍左中位数与右中位数的选择策略,通过LeetCode实例演示查找目标数的起始与终止位置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

二分查找:思路很简单,细节是魔鬼!

“传统的”二分查找法模板的问题
(1)取中位数索引的代码有问题

int mid = (left + right) / 2 

leftright 都比较大的时候,left + right 很有可能超过 int 类型能表示的最大值,即整型溢出,为了避免这个问题,应该写成:

int mid = left + (right - left) / 2 ;
or
int mid = (left + right) >>> 1 ;//推荐写法

(2)循环可以进行的条件写成 while (left <= right) 时,在退出循环的时候,需要考虑返回 left 还是 right

“神奇的”二分查找法模板
不需要考虑返回 left 还是 right

/**
     * 二分法模板
     * @param nums 二分法查找的数组
     * @param target 目标数
     * @return 目标数的下标
     */
    public int search(int[] nums, int target) {
        int len = nums.length;

        //左右边界
        int left = 0;
        int right = len - 1;

        // 退出循环条件,不必写出是返回left还是right
        while (left < right) {
            //取右中位数
            int mid = (left + right + 1) >>> 1;

            //分支逻辑,注意:写完分支逻辑后再看看是否有必要调整中位数
            if (nums[mid] > target) {
                right = mid - 1;
            } else {
                left = mid;
            }
        }

        if (nums[left] == target) {
            return left;
        }

        return -1;
    }
/*
 * 此题是leetcode.704    
 * 给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target,
 * 写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
 *
 * 示例 1:
 * 输入: nums = [-1,0,3,5,9,12], target = 9
 * 输出: 4
 * 解释: 9 出现在 nums 中并且下标为 4
 */

根据实际环境选择是左中位数还是右中位数,见下面例子(leetcode.34题)

左中位数:int mid = (left + right) >> 1
右中位数:int mid = (left + right + 1) >> 1
/**
 * @author mys
 * @version 2019.8.10
 */


package com.mys;

import org.junit.Test;

public class SearchRange34 {
    /**
     * 方法一:线性扫描
     * @param nums 数组
     * @param target 目标数
     * @return 目标数的起始位置和终止位置
     */
    public int[] searchRange1(int[] nums, int target) {
        //如果没有目标数,则返回{-1, -1}
        int[] targetRange = {-1, -1};

        //从左边开始扫描,如果找到目标数,记录位置作为起始位置,并中止循环
        for (int i = 0; i < nums.length; i ++) {
            if (nums[i] == target) {
                targetRange[0] = i;
                break;
            }
        }

        //如果扫描一遍过后,没有找到目标数,即targetRange[0] == -1
        //说明没有目标数,返回{-1, -1}
        if (targetRange[0] == -1) {
            return targetRange;
        }

        //现在从右边开始扫描,查找目标数的中止位置
        for (int j = nums.length - 1; j >= 0; j --) {
            if (nums[j] == target) {
                targetRange[1] = j;
                break;
            }
        }

        return targetRange;
    }

    public int[] searchRange2(int[] nums, int target) {
        //如果没有目标数,则返回{-1, -1}
        int[] targetRange = {-1, -1};

        int len = nums.length;

        //预判
        if (len == 0) {
            return targetRange;
        }

        targetRange[0] = findLowerBound(nums, target);

        //细节:如果左边界搜索不到,右边界就没有必要搜索了
        if (targetRange[0] == -1) {
            return targetRange;
        }

        targetRange[1] = findUpBoud(nums, target);

        return targetRange;
    }

    /**
     * 找到target第一个索引
     * 逻辑:当 target == mid 时,并不能确定mid是起始位置,但一旦 mid < target 时,mid及mid左边肯定不是起始位置
     *      mid右边的数 >= target时,当 mid=target 的第一个索引位置就是 起始位置
     * @param nums
     * @param target
     * @return
     */
    private int findLowerBound(int[] nums, int target) {
        int len = nums.length;

        int left = 0;
        int right = len - 1;

        while (left < right) {
            //左中位数
            int mid = (left + right) >>> 1;

            //分支逻辑
            if (nums[mid] < target) {
                left = mid + 1;
            } else {
                right = mid;
            }
        }

        //如果不存在目标数
        if (nums[left] != target) {
            return -1;
        }

        return left;
    }

    /**
     * 找到target最后一个索引
     * 逻辑:当 target==mid 时,同样不能确定是否为中止位置,但一旦 mid>target 时, mid及mid右边的肯定不是中止位置
     *      因此, mid左边的数 <=target 时,当 mid=target 的最后一个索引就是 中止位置
     * @param nums
     * @param target
     * @return
     */
    private int findUpBoud(int[] nums, int target) {
        int len = nums.length;

        int left = 0;
        int right = len - 1;

        //分支逻辑
        while (left < right) {
            //右中位数
            int mid = (left + right + 1) >>> 1;

            if (nums[mid] > target) {
                right = mid - 1;
            } else {
                left = mid;
            }
        }

        //如果不存在目标数
        if (nums[left] != target) {
            return -1;
        }

        return left;
    }

    @Test
    public void fun() {
        int[] nums = {5,7,7,7,8,10};
        int target = 7;
        int[] result = searchRange2(nums, target);
        System.out.println("{" + result[0] + ", " + result[1] + "}");
    }
}

参考链接:https://leetcode-cn.com/problems/search-insert-position/solution/te-bie-hao-yong-de-er-fen-cha-fa-fa-mo-ban-python-/

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值