文章目录
二分查找
常见困惑
当我们在刷leetcode关于二分查找的算法题的时候,经常会遇到下面的一些困惑:
- 边界搞不清,太闹心了
while (left <= right) Or while (left < right)
- 不知道新查找区间是mid 还是mid+1 还是mid-1
binarySearch(nums, target, mid+1, right) Or binarySearch(nums, target, mid, right)
- 二分查找一变形,就啥也不会了!
这篇博客解决什么问题
- 统一
边界
的写法,记住一种就够了,不用纠结left <= right
还是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;
}
上面就是一个二分查找非递归方式最最常见的一个模版,基本上大家都会,也不是这篇博客的重点。
递归方式(对!!就是它)
大家写递归的时候,容易陷入一个误区,总是有意识的“跟着递归函数”走,殊不知,一下子就把自己绕晕了。毕竟对于复杂一点的递归,压栈太深了,实在难以理解。
**正确的方式:**我学习网上大佬的方式,我们写递归的时候,只要关注递归函数本身的功能
,不要去纠结函数怎么走,只想着递归函数能干什么
,同时想一想这个递归的结束条件
是什么。只要明白了上面两点,那么其他的就交给递归函数自己来完成吧。
public static void main(String args[]) {
int[] nums = {1,2,3,4,5};
target = 3;
// 调用函数
searchIndex(nums, target, 0, nums.length-1);
}
public void searchIndex(int[] nums, int target, int low, int high) {
// 1. 递归结束的条件
if (low > high)
return ;
// 2. 递归函数功能体
int mid = (low + high) / 2;
if (nums[mid] == target) {
return mid;
} else if (target < nums[mid]) {
searchIndex(nums, target, low, mid -1);
} else if (target > nums[mid]) {
searchIndex(nums, target, mid+1, high);
}
}
注意:这种写法,不用考虑结left <= right
还是 left < right
问题。
如何在排序数组中查找元素的第一个和最后一个位置
假设有这样的情况:
在 nums = {1, 2, 2, 3, 4}
中查找 target = 2 并且下标最小
的元素所在的索引。
又或者:
在 nums = {1, 2, 3, 3, 3, 4}
中查找 target = 3 并且下标最大
的元素所在的索引。
又或者,看这个题目:在排序数组中查找元素的第一个和最后一个位置
给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
进阶:你可以设计并实现时间复杂度为 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]
递归模版只加一句代码,就可以完美解决上面的问题
查找 target 并且下标最小
的元素所在的索引
int ans = -1;
// 寻找第一个位置
public void searchLeft(int[] nums, int target, int low, int high) {
if (low > high)
return ;
int mid = (low + high) / 2;
if (nums[mid] == target) {
ans = mid;
// ***** 只增加这一行代码 *******
searchLeft(nums, target, low, mid -1);
} else if (target < nums[mid]) {
searchLeft(nums, target, low, mid -1);
} else if (target > nums[mid]) {
searchLeft(nums, target, mid+1, high);
}
}
怎么理解?
对于 nums = {1, 2, 2, 3, 4}
,查找target = 2的索引
- 首先,一般形式的二分查找,我们很容易找到索引 index = 2;找到之后就直接return index;
- 但是我们希望找到的是index = 1 这个索引,那么index = 1 是不是可以通过 在
nums = {1, 2 }
这一个范围找到呢? - 所以这里就是用到了递归的方法了,我们递归函数要解决的是:查找 target 并且
下标最小
的元素所在的索引。所以在步骤1 找到index = 2 的时候,不要着急返回return index;我们不妨再去index = 2的左边看看,或许可以再找到一个更小的索引,这就是为什么只需要增加一行代码:searchLeft(nums, target, low, mid -1);
同理:查找 target = 2 并且下标最大
的元素所在的索引。
int ans = -1;
// 寻找第一个位置
public void searchRight(int[] nums, int target, int low, int high) {
if (low > high)
return ;
int mid = (low + high) / 2;
if (nums[mid] == target) {
ans = mid;
// ***** 只增加这一行代码 *******
searchRight(nums, target, low, mid -1);
} else if (target < nums[mid]) {
searchRight(nums, target, low, mid -1);
} else if (target > nums[mid]) {
searchRight(nums, target, mid+1, high);
}
}
我们在找到index = 2 的时候,不妨那个 再去index = 2的右边看看,或许可以再找到一个=更大的索引==,这就是为什么只需要增加一行代码:searchRight(nums, target, mid+1, high);
写一道Leetcode的题目练练手吧!
看这个题目:在排序数组中查找元素的第一个和最后一个位置
给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
进阶:你可以设计并实现时间复杂度为 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]
按照刚才的套路,代码如下:
class Solution {
int[] ans = {-1,-1};
public int[] searchRange(int[] nums, int target) {
searchLeft(nums, target, 0, nums.length-1);
searchRight(nums, target, 0, nums.length-1);
return ans;
}
// 寻找第一个位置
public void searchLeft(int[] nums, int target, int low, int high) {
if (low > high)
return ;
int mid = (low + high) / 2;
if (nums[mid] == target) {
ans[0] = mid;
searchLeft(nums, target, low, mid -1);
} else if (target < nums[mid]) {
searchLeft(nums, target, low, mid -1);
} else if (target > nums[mid]) {
searchLeft(nums, target, mid+1, high);
}
}
// 寻找最后一个位置
public void searchRight(int[] nums, int target, int low, int high) {
if (low > high)
return ;
int mid = (low + high) / 2;
if (nums[mid] == target) {
ans[1] = mid;
searchRight(nums, target, mid+1, high);
} else if (target < nums[mid]) {
searchRight(nums, target, low, mid -1);
} else if (target > nums[mid]) {
searchRight(nums, target, mid+1, high);
}
}
}