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;
}
写个小总结:
- 排序好的数组,目标值target
- left 和 right 指针
- 比较目标值和array[middle] (middle = (left + right) / 2)
- 更新指针, 注意left = middle + 1,right = middle -1; 加一减一是因为被比较的array[middle] 在下一轮不必再出现了,毕竟它!=target
上面属于是二分查找的基础,现在看一个稍有难度题力扣题目链接
描述:给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
进阶:你可以设计并实现时间复杂度为 的算法解决此问题吗?
示例 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;
}
}