题目
给定一个 n
个元素有序的(升序)整型数组 nums
和一个目标值 target
,写一个函数搜索 nums
中的 target
,如果目标值存在返回下标,否则返回 -1
。
示例 1:
输入:nums
= [-1,0,3,5,9,12],target
= 9 输出: 4 解释: 9 出现在nums
中并且下标为 4
示例 2:
输入:nums
= [-1,0,3,5,9,12],target
= 2 输出: -1 解释: 2 不存在nums
中因此返回 -1
方法一:单边查询
public class Solution {
public int Search(int[] nums, int target) {
int searchIndex=nums.Length;
for(int i=0;i<searchIndex;i++)
{
if(nums[i]==target) return i;
}
return -1;
}
}
非常简单的单边从左到右依次进行判断,时间击败48%,内存击败98%
方法二:双边查询
public class Solution {
public int Search(int[] nums, int target) {
int searchIndex=nums.Length;
for(int i=0;i<searchIndex;i++)
{
if(nums[i]==target) return i;
if(nums[searchIndex-i-1]==target) return searchIndex-i-1;
}
return -1;
}
}
左右同时开始判断,时间击败89%,内存击败96%
方法三:二分法
双边查询,如果值在数列中间则很难查询到,利用二分法,先判断两端,再一直迭代判断中点
public class Solution {
public int Search(int[] nums, int target) {
int left=0;
int right=nums.Length-1;
int middle=0;
if(nums[left]==target) return left;
if(nums[right]==target) return right;
if(left==right) return -1;
while(right-left!=1){
middle=(left+right)/2;
if(nums[middle]==target) return middle;
else if(nums[middle]<target) left=middle;
else right=middle;
}
return -1;
}
}
时间击败79%,内存击败84%,结果反而不如双边,总感觉代码很拖沓,且未通过长度为1的数列
方法四 真正的二分法
简单看了一下视频,发现我的二分法中出现的区别,middle进行多次的判断,应当赋值middle+1或者-1,重新进行书写
学习代码的视频:手把手带你撕出正确的二分法 | 二分查找法 | 二分搜索法 | LeetCode:704. 二分查找_哔哩哔哩_bilibili
左闭右闭区间
public class Solution {
public int Search(int[] nums, int target) {
int left=0;
int right=nums.Length-1;
int middle=0;
while(left<=right)
{
middle=(right+left)/2;
if(nums[middle]==target) return middle;
else if(nums[middle]<target) left=middle+1;
else right=middle-1;
}
return -1;
}
时间超过48%,内存超过60% 效率最高的居然还是双边,不知道是什么原因,等以后深入了解c#后再来回答
左闭右开
public class Solution {
public int Search(int[] nums, int target) {
int left=0;
int right=nums.Length;
int middle=0;
while(left<right)
{
middle=(right+left)/2;
if(nums[middle]==target) return middle;
else if(nums[middle]<target) left=middle+1;
else right=middle;
}
return -1;
}
}
左开右闭
public class Solution {
public int Search(int[] nums, int target) {
int left=-1;
int right=nums.Length-1;
int middle=0;
while(left<right)
{
middle=left+(right-left)/2+1;
if(nums[middle]==target) return middle;
else if(nums[middle]<target) left=middle;
else right=middle-1;
}
return -1;
}
}
总结
在二分法中,有几大要点会导致在某些序列中会导致一直循环的问题分别是:
1.选择搜索区间
2.在不等于target情况下,left 和 right 的取值问题
2.middle值的选择
搜索区间
在二分法中搜索区间尤为关键,区间决定了 left 和 right 的取值,以及 while 中的 left与right比较问题。
三种不同区间的left,right取值如上述代码,总的概括就是
- left 和 right 的取值需要保证序列全部值在区间内
- left 与 right 比较需要保证在区间内的值均被遍历
在不等于target情况下,left 和 right 的取值问题
这类的区间问题需要保证的就是 需要遍历的值被遍历,且仅被遍历一次 在某些情况下,当被遍历时如果多次遍历同一值会陷入死循环
middle值的选取
这个问题出现自 左开右闭 和 左闭右开 的代码区别上,很显然,两者是 对称 的,我们会下意识的简单的把不等于target情况下的 left 和 right 的取值 调换,但是这样做,左开右闭的情况下会造成死循环。
猜测原因:可能是因为在取middle的过程中,/2这一步骤是向下取整导致陷入死循环,当左开的时候需要实现向上取值,即+1,便不会陷入死循环中
题外tips 在写middle=(left+right)/2时尽量写成middle=left+(right-left)/2,可以有效防止(left+right)造成的溢出。
左开右闭的办法来自于:二分查找的细节(左闭右闭、左闭右开、左开右闭)及其两段性-CSDN博客