1 二分查找
分析:
这是一道很简单的二分法题,定义两个指针和中间值middle,判断middle对应数组值与目标值的大小关系,从而对left和right进行修改。由于太过基础,代码简单基础就不多赘述。
目录
1 二分查找
分析:
代码展示:
2 在排序数组中查找元素的第一个和最后一个位置
分析:
代码展示:
3 搜索插入位置
分析:
代码展示:
4 x 的平方根
分析:
代码展示:
class Solution {
public int search(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while(left <= right) {
int middle = left + (right - left) / 2;
if(nums[middle] < target) {
left = middle + 1;
}else if (nums[middle] > target) {
right = middle -1;
}else {
return middle;
}
}
return -1;
}
}
2 在排序数组中查找元素的第一个和最后一个位置
分析:
这道题要求时间复杂度为logn,如果遍历数组时间复杂度最坏就是n,而时间复杂度为logn那就需要用二分法。
注意 二分法不一定要求数组有序,只要该数组存在二段性就可以用二分法。何为二段性,就是通过一个性质可以把数组划分为两段。比如在这道题中我们如果找第一个目标值就可以划分为小于target的为左端,大于等于targert的为右端。
我们定义两个指针left 和 right 分别指向下标0和下标nums.length - 1 。拿案例一分析,left到第一个最后一个7是小于8的区域,第一个8到right是大于等于8的区域。我们用middle来判断当前值与目标值的大小情况。如果小于目标值那么就让left = middle + 1;如果大于那么就让right = middle。
这时我们会发现如果目标值存在于数组,那么left最后一定会指向第一个目标值,因为小于目标值那么就会让left = middle +1,所以必然有middle + 1等于目标值,而且一定是第一个目标值。
代码展示:
class Solution {
public int[] searchRange(int[] nums, int target) {
if(nums.length == 0) {
return new int[]{-1,-1};
}
int left = 0;
int right = nums.length - 1;
int[] arr = new int[2];
//找第一个数
while(left < right) {
int middle = left + (right - left) / 2;
if(nums[middle] < target) {
left = middle + 1;
}else {
right = middle;
}
}
if(nums[left] == target)
arr[0] = left;
else arr[0] = -1;
//找最后一个数
left = 0;
right = nums.length - 1;
while(left < right) {
int middle = left + (right - left + 1) / 2;
if(nums[middle] <= target) {
left = middle;
}else{
right = middle - 1;
}
}
if(nums[left] == target) arr[1] = right;
else arr[1] = -1;
return arr;
}
}
这里有很多细节:
1 while循环的条件必须是left < right:
第一点是因为,如果两个指针相遇那么一定会出结果只需要判断最后是不是目标值即可。第二点是因为(核心),如果条件是left <= right,那么代码就会死循环,当两个指针同时指向目标值时,由于nums[middle] = target,因此right = middle。此时就会不断循环。
2 我们可以看到寻找第一个数和最后一个数的middle求法是不一样的。middle = left + (right - left) / 2 这个求出的中间数是偏左的(比如left指向0下标,right指向1下标,那么这个公式求出的middle最后会指向0下标,相反middle = left + (right - left + 1)/2指向的就是指向1下标),由于上述我们提到如果数组中存在目标值,那么left最终一定会指向第一个目标值,所以我们要求middle是要偏向left一方,还有一个原因就是避免死循环,如果middle偏向right一方,那么right = middle;而求出的middle又指向right。
求最后一个数和求第一个数原理相同,只需要将letf到小于等于目标值划分为左区域,大于目标值划分为有区域,一旦nums[middle[大于目标值就让right = middle - 1.那么如果数组中存在该目标值,right一定会指向目标值的最后一位。
3 搜索插入位置
分析:
做完了上一道题,这道题就显得很简单了。我们看示例二,如果目标值是2,那么我们输出的结果是3的下标。我们可以想到,将小于目标值到left划分为一个区域,大于目标值到right划分为一个区域,那么当nums[middle]小于目标值时,left = middle + 1就能找到最后的结果。我们可以总结为第一个大于目标值的数等同于找第一个目标值,最后一个小于目标值的数等同于找最后一个目标值,方法都是一样的。
代码展示:
class Solution {
public int searchInsert(int[] nums, int target) {
int left = 0 ;
int right = nums.length - 1;
if(nums[0] > target) return 0;
if(nums[right] < target) return right + 1;
while(left < right) {
int middle = left + (right - left) / 2;
if (nums[middle] < target) {
left = middle + 1;
}else{
right = middle;
}
}
return left;
}
}
前两行代码是出去特殊情况,后面方法则等同于上一道题。
4 x 的平方根
分析:
这道题我们仍然可以用二分法,比如定义一个x长的数组,并求出每一个数组下标的平方值,如果平方值是最后一个小于或者等于目标值的那么就返回该下标。二位破门上面总结了,如果是求最后一个小于目标值的,那么right = middle - 1一定会指向最后一个小于或者等于该目标值的下标。
代码展示:
class Solution {
public int mySqrt(int x) {
if(x < 1) return 0;
long left = 1;
long right = x / 2;
while(left < right) {
long middle = left + (right - left + 1) / 2;
if(middle * middle > x) right = middle - 1;
else left = middle ;
}
return (int)left;
}
}
这里我们要考虑,范围,由于middle * middle结果太大所以需要用到Long类型,并且最后返回值要强转。