目录
问题一,为什么循环的条件是left<=right ,为什么要有等号呢
问题二,为什么中间值是left +(right - left) / 2
二分查找
思路
总体
二分查找前提是已经是从小到大排序了,就是对半着查找
一开始先看最中间那个
如果等于,那就找到了,直接返回
如果目标小于中间值,那么把右边界赋值为中间值减一
如果目标大于中间值,那么把左边界赋值为中间值加一
细节
问题一,为什么循环的条件是left<=right ,为什么要有等号呢
-
当
left == right
时,搜索区间仍然包含 一个元素(即nums[left]
或nums[right]
),需要检查这个元素是否等于target
。
问题二,为什么中间值是left +(right - left) / 2
(right - left)只是偏移量,在0~5是可以用的, 但是如果最左边不是0,那就错了,要加上最左边
一开始写的是(left +right)/2 但是这样有可能会溢出 ,超出java int类型的最大值,导致结果是负数, java int最大值是2,147,483,647(2³¹ - 1) ,java的第一位是符号位,如果你超出了最大值,就会变成负数,负数除2还是负数。
int max = Integer.MAX_VALUE; // 2147483647 (0x7FFFFFFF) int overflow = max + 1; // 溢出后变为 -2147483648 (0x80000000)
注意:
>>是
有符号右移 / 算术右移 ,移了之后原符号不变
>>>
(无符号右移 / 逻辑右移), ,移了之后原符号可能改变
可以采用
方法一:left +(right - left) / 2
方法二:(left +right)>>>1,是向下取整的
虽然说超出了是负数,但其实二进制的数还是一样的,只是java把第一位看作是符号位,所以你向右移一位,其实就是除二向下取整
问题三,为什么最后返回的是左边的值呢
情况 1:target
存在于数组中
-
在循环中直接返回
mid
,不会走到最后的return
语句。
情况 2:target
不存在于数组中
-
left
的物理意义:-
它是
target
应该插入的位置,即第一个比target
大的元素的位置。 -
如果所有元素都比
target
小,left
会停在len(nums)
(数组末尾之后)。 -
如果所有元素都比
target
大,left
会停在0
(数组开头)。
-
-
right
的问题:-
right
指向的是最后一个比target
小的元素,插入后会破坏顺序。 -
例如
nums = [1,3,5,6]
,target = 2
:-
终止时
left = 1
,right = 0
。 -
插入到
left
(1)是正确的[1, **2**, 3, 5, 6]
,而right
(0)会错误地插入到开头。
-
-
例题1
代码实现
Java
package Test;
import java.util.HashMap;
import java.util.Map;
class Solution {
public static int searchInsert(int[] nums, int target) {
int length = nums.length;
int left = 0;
int right = length - 1;
while (left <= right) {
int mid = left +(right - left) / 2;
//为什么是这样呢,(right - left)只是偏移量,在0~5是可以用的
// 但是如果最左边不是0,那就错了,要加上最左边
if (nums[mid] == target) {
return mid;
}
else if(target<nums[mid]){
right = mid - 1;
}
else if (target>nums[mid]) {
left = mid + 1;
}
}
return left;
}
public static void main(String[] args) {
int[] nums = {1,3,5,6};
System.out.println(searchInsert(nums, 2));
for (int num : nums) {
System.out.print(num);
}
}
}
Python
注意python中,/是有小数点的除 // 是整除,向下取整
class Solution:
def searchInsert( self,nums, target):
length = len(nums)
left = 0
right = length - 1
while left<=right:
mid=left+(right-left)//2
if target==nums[mid]:
return mid
if target<nums[mid]:
right=mid-1
if target>nums[mid]:
left=mid+1
return left;
solution = Solution()
nums=[1,3,5,6]
print(solution.searchInsert(nums, 2)) # 输出: 1
例题2
Java
class Solution {
public int search(int[] nums, int target) {
int length = nums.length;
int left = 0;
int right = length - 1;
while (left <= right) {
int mid = left +(right - left) / 2;
//为什么是这样呢,(right - left)只是偏移量,在0~5是可以用的
// 但是如果最左边不是0,那就错了,要加上最左边
if (nums[mid] == target) {
return mid;
}
else if(target<nums[mid]){
right = mid - 1;
}
else if (target>nums[mid]) {
left = mid + 1;
}
}
return -1;
}
}
Python
class Solution(object):
def search(self, nums, target):
mid=-1
left=0
right=len(nums)-1
while left<=right:
mid=left+(right-left)//2
if(nums[mid]==target):
return mid
elif(target<nums[mid]):
right=mid-1
else:left=mid+1
return -1
例题三
Java
在这次写法中,使用二分法之前,我先判断了数组是不是为空,和目标值在不在范围里面,可以避免无效的搜索,是一个优化的方法
package Test;
import java.util.HashMap;
import java.util.Map;
class Solution {
public static int[] searchInsert(int[] nums, int target) {
int length = nums.length;
int left = 0;
int right = length - 1;
int leftcadidate=-1;
int rightcadidate=-1;
int[]result={leftcadidate,rightcadidate};
//情况1,数组为空
if(length==0)
return result;
//情况2,目标值超出范围
// 我们知道了这个数组是递增的,那么我们先取边界值和目标值比较,如果不再范围内直接就返回了
if(target<nums[0]||target>nums[length-1])
return result;
//情况三:目标值在范围里面
while (left <= right) {
int mid = left +(right - left) / 2;
if (nums[mid] == target) {
leftcadidate = mid;
right = mid - 1;
}
else if(target<nums[mid]){
right = mid - 1;
}
else if (target>nums[mid]) {
left = mid + 1;
}
}
left = 0;
right = length - 1;
while (left <= right) {
int mid = left +(right - left) / 2;
if (nums[mid] == target) {
rightcadidate=mid;
left = mid + 1;
}
else if(target<nums[mid]){
right = mid - 1;
}
else if (target>nums[mid]) {
left = mid + 1;
}
}
result[0]=leftcadidate;
result[1]=rightcadidate;
return result;
}
public static void main(String[] args) {
int[] nums = {5,7,7,8,8,10};
int target = 6;
int [] result=searchInsert(nums, target);
for (int i = 0; i < result.length; i++) {
System.out.println(result[i]);
}
}
}
Python
class Solution(object):
def searchRange(self, nums, target):
mid=-1
left=0
right=len(nums)-1
leftcandidate=-1
while left<=right:
mid=left+(right-left)//2
if(target<nums[mid]):
right=mid-1
elif(nums[mid]<target):
left=mid+1
else:
right=mid-1
leftcandidate=mid
mid = -1
left = 0
right = len(nums)-1
rightcandidate = -1
while left <= right:
mid = left + (right - left) // 2
if (target < nums[mid]):
right = mid - 1
elif (nums[mid] < target):
left = mid + 1
else:
left=mid+1
rightcandidate = mid
return [leftcandidate,rightcandidate]
总结
1.数组的长度是length,但是数组是从零开始,使用数组的最后一个数字下标是length-1
2.可以先判断边界值和数组是否为空来优化算法
3.java创建数组是 int nums[]={1,2,3,4,5};
python是 nums=[1,2,3,4,5]
4.数组有可能为空,调用nums.length 会抛出异常,所以最开始要先判断是否为空(为空和数组长度为0是不一样的)(我的代码没有加入这个)。
在python,可以使用
if nums is None:
return [-1,-1]
来判断是否为空