二分查找各种类型例题

普通二分查找

二分法中left + (right - left) /2 就和 (left + right) / 2 的结果相同,但是有效防⽌了 left 和right 太⼤直接相加导致溢出。

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

1、为什么 while 循环的条件中是 <=,⽽不是 <?
答:因为初始化 right 的赋值是 nums.length - 1 ,即最后⼀个元素的索引,⽽不是 nums.length 。
这⼆者可能出现在不同功能的⼆分查找中,区别是:前者相当于两端都闭区间 [left, right] ,后者相当于左闭右开区间 [left, right) ,因为索引大小为 nums.length 是越界的。

我们这个算法中使⽤的是前者 [left, right] 两端都闭的区间。这个区间其实就是每次进⾏搜索的区间。
什么时候应该停⽌搜索呢?当然,找到了⽬标值的时候可以终⽌:
if(nums[mid] == target)
return mid;
但如果没找到,就需要 while 循环终⽌,然后返回 -1。那 while 循环什么时候应该终⽌?搜索区间为空的时候应该终⽌,意味着你没得找了,就等于没找到嘛。

while(left <= right) 的终⽌条件是 left == right + 1 ,写成区间的形式就是 [right + 1, right] ,或者带个具体的数字进去 [3, 2] ,可⻅这时候区间为空,因为没有数字既⼤于等于 3 ⼜⼩于等于 2 的吧。所以这时候while 循环终⽌是正确的,直接返回 -1 即可。
while(left < right) 的终⽌条件是 left == right ,写成区间的形式就是[left, right] ,或者带个具体的数字进去 [2, 2] ,这时候区间⾮空,还有⼀个数 2,但此时 while 循环终⽌了。也就是说这区间 [2, 2] 被漏掉了,索引 2 没有被搜索,如果这时候直接返回 -1 就是错误的。

此算法有什么缺陷?
答:⾄此,你应该已经掌握了该算法的所有细节,以及这样处理的原因。但是,这个算法存在局限性。
⽐如说给你有序数组 nums = [1,2,2,2,3] , target 为 2,此算法返回的索引是 2,没错。但是如果我想得到 target 的左侧边界,即索引 1,或者我想得到 target 的右侧边界,即索引 3,这样的话此算法是⽆法处理的

寻找左侧边界的⼆分搜索

int left_bound(int[]nums,int target){
    if(nums.length == 0) return -1;
    int left = 0;
    int right = nums.length;//注意这里
    while(left < right){
        int mid = (left + right)/2;
        if(nums[mid] == target) right = mid;
        else if(nums[mid] < target ) left = mid+1;
        else if(nums[mid] > target) right  = mid;
    }
   return nums[left] == target ? left : -1;
}
另一种写法:
     int left_bound(int[]nums,int target){
    int left = 0;
		int right = nums.length - 1;
		while (left <= right) {
			int mid = (right - left) / 2 + left;
			if (nums[mid] == target) {
				right = mid - 1;
			} else if (nums[mid] < target) {
				left = mid + 1;
			} else if (nums[mid] > target) {
				right = mid - 1;
			}
		}
		// 如果退出循环了,也就是left = right + 1
		// 判断下越界了不
		if (left >= nums.length || nums[left] != target) {
			return -1;
		}
		return left;

}

1、为什么 while 中是 < ⽽不是 <= ?
答:⽤相同的⽅法分析,因为 right = nums.length ⽽不是 nums.length -1 。因此每次循环的「搜索区间」是 [left, right) 左闭右开。
while(left < right) 终⽌的条件是 left == right ,此时搜索区间 [left,left) 为空,所以可以正确终⽌。

PS:这⾥先要说⼀个搜索左右边界和上⾯这个算法的⼀个区别,也是很多读者问的:刚才的 right 不是 nums.length - 1 吗,为啥这⾥⾮要写成nums.length 使得「搜索区间」变成左闭右开呢?
因为对于搜索左右侧边界的⼆分查找,这种写法⽐较普遍,我就拿这种写法举例了,保证你以后遇到这类代码可以理解。你⾮要⽤两端都闭的写法反⽽更简单,我会在后⾯写相关的代码,把三种⼆分搜索都⽤⼀种两端都闭的写法统⼀起来,你耐⼼往后看就⾏了

2.为什么该算法能够搜索左侧边界?
答:关键在于对于 nums[mid] == target 这种情况的处理:
if (nums[mid] == target)
right = mid;

可⻅,找到 target 时不要⽴即返回,⽽是缩⼩「搜索区间」的上界right ,在区间 [left, mid) 中继续搜索,即不断向左收缩,达到锁定左侧边界的⽬的。

查找第一个大于等于给定值的元素

public static int search(int[] nums, int val) {
int low = 0, high = nums.length - 1;
while (low <= high) {
int mid = (low + high) >>> 1;
if (nums[mid] < val) {
low = mid + 1;
} else {
// 如果nums[mid]是第一个元素,或者nums[mid-1]小于val
// 说明nums[mid]就是第一个大于等于给定值的元素
if (mid == 0 || nums[mid - 1] < val) {
return mid;
}
high = mid - 1;
}
}
return -1;
}

或者这样:

public static int search(int[] nums, int val) {
int low = 0, high = nums.length - 1,index=0;
while (low <= high) {
int mid = (low + high) >>> 1;
if (nums[mid] < val) {
low = mid + 1;
} else {
// 如果nums[mid]是第一个元素,或者nums[mid-1]小于val
// 说明nums[mid]就是第一个大于等于给定值的元素
index=mid;
high = mid - 1;
}
}
return index;
}

查找最后一个小于等于给定值的元素

public class Code06_BSNearRight {

    //在arr上,找满足<=value的最右位置    O(logn)
    public static int nearestIndex(int[] arr,int value){
        int L=0;
        int R=arr.length-1;
        int index = -1;//记录最左边的对号
        while(L<=R){
            int mid=L+((R-L)>>1);
            //发现左侧有小于目标值的,抛弃左边,左边下标L=mid+1
            if(arr[mid]<=value){
                index=mid;
                L=mid+1;
            }else {
                R=mid-1;
            }
        }
        return index;
    }

找到小于等于目标值的最大位置

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


<think>好的,用户想要找关于二分查找的简单例题和示例代码。首先,我需要回忆二分查找的基本概念和应用场景。二分查找是一种高效的搜索算法,适用于有序数组,时间复杂度为$O(\log n)$。用户提到“简单例题”,所以应该找一些基础的题目,比如在有序数组中查找目标值,或者类似的问题。 接下来,我需要查看用户提供的引用内容,看看是否有相关的例题或代码示例。引用[1]提到了LeetCode 744题,寻找比目标字母大的最小字母,这应该是一个二分查找的应用。引用[2]是关于第K个缺失的正整数,虽然题目涉及严格升序数组,但可能需要结合其他方法,可能对用户来说稍微复杂。引用[3]和[4]中提到了二分查找的代码实现和一些应用,比如求次方根或猜数字游戏。 用户需要的是简单例题,所以LeetCode 704题(二分查找)应该是最基础的,直接在一个有序数组中查找目标值的位置。这也是大多数教材中提到的标准二分查找问题。另一个例子是LeetCode 35题(搜索插入位置),同样适合初学者练习。744题作为稍微进阶一点的题目,但代码结构类似,可以作为第二个例题。 然后,我需要确保提供的示例代码正确,并且符合用户的要求。需要检查代码是否使用正确的二分模板,比如循环条件、中间值计算和边界更新。例如,在标准二分查找中,通常使用左闭右闭或左闭右开区间,循环条件为`while left <= right`,中间值计算为`mid = left + (right - left) // 2`以避免溢出。 在编写回答时,需要分步骤解释每个例题,说明问题描述、解决思路和代码实现。同时,要引用用户提供的资料,比如引用[1]中的744题和引用[4]中的猜数字代码结构。但要注意,用户请求的是简单例题,所以可能优先选择更基础的题目,如704和35题,再提到744作为扩展。 最后,生成相关问题,帮助用户进一步学习。例如,询问二分查找的边界条件处理、时间复杂度的分析,或者变种问题如查找第一个出现的位置。这些相关问题可以引导用户深入理解二分查找的不同应用场景和细节处理。</think>### 二分查找基础例题及代码示例 #### 例题1:有序数组中查找目标值(LeetCode 704) **问题描述** 给定一个升序整数数组 `nums` 和目标值 `target`,返回 `target` 在数组中的索引,若不存在则返回 `-1`。 **解决思路** 1. 初始化左右边界 `left=0`, `right=len(nums)-1` 2. 循环条件 `while left <= right` 3. 计算中间索引 `mid = left + (right - left) // 2`(避免整数溢出) 4. 根据 `nums[mid]` 与 `target` 比较调整边界 **示例代码** ```python def search(nums, target): left, right = 0, len(nums) - 1 while left <= right: mid = left + (right - left) // 2 if nums[mid] == target: return mid elif nums[mid] < target: left = mid + 1 else: right = mid - 1 return -1 ``` --- #### 例题2:寻找比目标字母大的最小字母(LeetCode 744)[^1] **问题描述** 给定一个升序字符数组 `letters` 和目标字母 `target`,寻找比 `target` 大的最小字母(若不存在则返回首字母)。 **解决思路** 1. 处理边界情况:若 `target >= letters[-1]`,返回首字母 2. 二分查找第一个比 `target` 大的字母 **示例代码** ```python def nextGreatestLetter(letters, target): left, right = 0, len(letters) - 1 if target >= letters[right]: return letters[0] while left <= right: mid = left + (right - left) // 2 if letters[mid] <= target: left = mid + 1 else: right = mid - 1 return letters[left] ``` --- #### 例题3:搜索插入位置(LeetCode 35) **问题描述** 在有序数组中查找目标值,若不存在则返回应插入的位置。 **代码实现** ```python def searchInsert(nums, target): left, right = 0, len(nums) - 1 while left <= right: mid = left + (right - left) // 2 if nums[mid] == target: return mid elif nums[mid] < target: left = mid + 1 else: right = mid - 1 return left # 最终 left 即为插入位置 ``` --- ### 关键点总结 1. **边界条件**:循环终止条件 `left <= right` 确保所有元素都被检查 2. **中间值计算**:使用 `mid = left + (right - left) // 2` 避免整数溢出 3. **模板扩展**:根据问题需求调整比较逻辑(如例题2中寻找第一个大于目标的值) ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值