二分查找相关练习
文章目录
1.二分查找
思想
二分法:
1.即将一个完整的个体分成两份,即中间取名为mid,左边为left,右边为right。
2.然后将需要查找的数与mid对比,如果大于,则让left=mid,再次从中间取名mid,再次比较,以此往复。
3.直到找完为止,同理需要查找的数小于mid也一样,只不过这次变得是right=mid。
二分法查找有两种形式:
第一种为左闭右闭型,即数值可以从两边判断/获取。
第二种为左闭右开型,即数值只可以从左边判断/获取。最右边的取不到。
代码实现
第一种
public int search(int[] nums, int target) {
//二分查找
//左闭右闭区间
if(target<nums[0]||target>nums[nums.length-1]){
return -1;//如果target 不在数组范围之内则返回-1 表示没有找到
}
int left = 0,rigth = nums.length-1; // 定义target在左闭右闭的区间里,[left, right]
while (left<=rigth){// 当left==right,区间[left, right]依然有效,所以用 <=
//每次mid为left和right中间位置 即left+(right-left)/2 这里采用位运算
int mid=left+((rigth-left)>>1);// 防止溢出 等同于(left + right)/2
//这里就会有三种情况 等于 大于 小于
if(nums[mid]==target){
return mid;// 数组中找到目标值,直接返回下标
}
if(nums[mid]<target){// target 在右区间,所以[middle + 1, right]
left = mid+1;
}
if(nums[mid]>target){ // target 在左区间,所以[left, middle - 1]
rigth = mid-1;
}
}
//当在范围内没有找到,就返回-1
return -1;
}
第二种
public int search_back(int[] nums, int target) {
//二分查找 左闭右开
if(target<nums[0]||target>nums[nums.length]){
return -1;//如果target 不在数组范围之内则返回-1 表示没有找到
}
int left = 0,right = nums.length;// 定义target在左闭右开的区间里,即:[left, right)
while (left<right){// 因为left == right的时候,在[left, right)是无效的空间,所以使用 <
int mid = left+((right-left)>>1);
if(nums[mid]==target){
return mid;// 数组中找到目标值,直接返回下标
}else if(nums[mid]<target){
left = mid+1; // target 在右区间,在[middle + 1, right)中
}else if(nums[mid]>target){
right = mid; // target 在左区间,在[left, middle)中
}
}
// 未找到目标值
return -1;
}
2.搜索插入位置
思想
想要插入那么总共有4个插入位置
1.目标值在数组所有元素之前
2.目标值等于数组中某一个元素
3.目标值插入数组中的位置
4.目标值在数组所有元素之后
5.通过二分法查找目标值,没有就会跳出来,而跳出来的left就是需要添加的位置
代码实现
package com.algo.array;
public class Algo35 {
public int searchInsert(int[] nums, int target) {
/**
* 时间复杂度:
*
* 二分查找的时间复杂度为 O(log n),其中 n 是数组的长度。每次循环将查找范围缩小一半,因此时间复杂度是对数级别的。
* 空间复杂度:
*
* 该算法的空间复杂度为 O(1),因为只使用了有限的变量,没有使用额外的数据结构。
*/
//二分法查找是否有元素,有就返回索引,没有就返回要加入的索引
int left = 0;//数组左边
int right = nums.length - 1;//数组右边
int index = 0;//要返回的索引
while (left <= right) {
//(left+right)/2==left + ((right - left) >> 1)
int mid = left + ((right - left) >> 1);
if (nums[mid] > target) {//如果中间的值大于taget那么就将查找范围缩小到左边
right = mid - 1;//将查找范围缩小到左边
} else if (nums[mid] < target) {//如果中间的值小于target就将查找范围缩小到右边
left = mid + 1;//将查找范围缩小到右边
} else if (nums[mid] == target) {//如果找到该元素,返回其索引
index = mid;
break;
}
}
//如果没有找到就会跳出来,而跳出来的条件就是left>right
//而要加入的位置就是left
if (left > right) {
index = left;
}
return index;
}
}
3.x的平方根
思想
1.从中间变量mid=x/2开始查找x的平方根
2.temp=x/mid,当temp<mid时,x的平方根就在左边,当temp>mid时,x的平方根就在右边,以此反复。
3.如果mid==temp就找到了平方根的整数部分。
4.当循环结束时,ringht所指就是所求平方根的整数部分。
代码实现
package com.algo.array;
public class Algo69 {
public int mySqrt(int x) {
/**
* 这段代码采用了二分查找的方法来计算一个非负整数的平方根,时间复杂度主要取决于二分查找的次数。
*
* 初始化阶段,没有循环操作,时间复杂度为O(1)。
* 在 while 循环中,每次都将查找区间缩小一半,因此二分查找的时间复杂度为O(log x),其中x是输入的非负整数。
* 在找到平方根后,直接返回结果,时间复杂度为O(1)。
* 因此,总体时间复杂度为O(log x),其中x是输入的非负整数。
*
* 分析空间复杂度:
* 这段代码只使用了有限的几个变量,没有使用额外的数据结构,因此空间复杂度为O(1),即常数级别的空间复杂度。
*
* 综上所述,该代码的时间复杂度为O(log x),空间复杂度为O(1)。
*/
if(x==0|x==1){
return x;
}
int left = 1;//从1开始
int right = x;//因为一个数的平方不可能大于他本身
while (left<=right){
int mid = left + ((right-left)>>1);//定义中间变量
int temp = x/mid;//计算中间值的平方根
if(temp<mid){// 如果中间值的平方根小于中间值本身,说明平方根在左边
right = mid-1;
}else if(temp>mid){// 如果中间值的平方根大于中间值本身,说明平方根在右边
left = mid +1;
}else{// 如果中间值的平方根等于中间值本身,说明找到了平方根的整数部分
return mid;
}
}
return right;// 当循环结束时,right 指向的就是平方根的整数部分
}
}
4.在排序数组中查找元素的第一个和最后一个位置
力扣:34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(Leetcode)
思想
1.通过二分法查找元素位置,找到元素位置
2.找到元素位置后,以当前索引为根,分别向两边遍历,查找第一个位置和最后一个位置
3.向前遍历时注意索引前面是否还有元素,向后遍历时注意索引后面是否还有元素
4.向前遍历,当有元素且前一个元素的值也为目标值,则第一个索引向前推一
5.向后遍历,当有元素且后一个元素的值也为目标值,则第二个索引向后推一
代码实现
package com.algo.array;
public class Algo34 {
public int[] searchRange(int[] nums, int target) {
/**
* 这段代码采用了二分查找的方法来查找数组中目标元素的起始和结束索引,时间复杂度主要取决于二分查找的次数,以及在找到目标元素后进一步搜索起始和结束索引的次数。
*
* 初始化阶段,没有循环操作,时间复杂度为O(1)。
* 在 while 循环中,每次都将查找区间缩小一半,因此二分查找的时间复杂度为O(log n),其中n是数组的长度。
* 在找到目标元素后,进一步搜索起始和结束索引的过程是线性的,最坏情况下可能需要遍历整个数组,因此时间复杂度为O(n),其中n是数组的长度。
* 因此,总体时间复杂度为O(log n + n),其中n是数组的长度。
*
* 分析空间复杂度:
* 这段代码只使用了有限的几个变量,没有使用额外的数据结构,因此空间复杂度为O(1),即常数级别的空间复杂度。
*
* 综上所述,该代码的时间复杂度为O(log n + n),空间复杂度为O(1)。
*/
//二分法查找是否有元素,有就返回开始和结束的索引,没有就返回[-1,-1]
int left = 0;//数组左边
int right = nums.length - 1;//数组右边
int[] index = {-1, -1};//默认值为 -1 -1
while (left <= right) {
int mid = left + ((right-left)>>1);
if(nums[mid]>target){
right = mid - 1;
}else if(nums[mid]<target){
left = mid + 1;
}else{
//到这里说明找到target的元素了 当前索引为mid
//找到元素就可以覆盖[-1,-1]
index[0] = mid;
index[1] = mid;
//当前索引的值有一个是target
while (nums[index[0]]==target|nums[index[1]]==target){
//如果当前索引(index[0])>0 并且 当前节点的前一为也是target nums[index[0]-1]==target
if(index[0]>0&&nums[index[0]-1]==target){
//索引前移
index[0]--;
//如果当前索引(index[1])<nums.length-1 并且 当前节点的后一为也是target nums[index[1]+1]==target
}else if(index[1]<nums.length-1&&nums[index[1]+1]==target){
//索引后移
index[1]++;
}else{
break;
}
}
break;
}
}
return index;
}
}
5.有效的完全平方数
力扣:367. 有效的完全平方数 - 力扣(Leetcode)
思想
做这个题之前先做x的平方根,因为这两个几乎一模一样。
1.从中间变量mid=x/2开始查找x的平方根
2.temp=x/mid,当temp<mid时,x的平方根就在左边,当temp>mid时,x的平方根就在右边,以此反复。
3.如果mid==temp就找到了平方根的整数部分。
4.当循环结束时,ringht所指就是所求平方根的整数部分。
5.找到平方根的整数部分后再进行判断,如果整数部分不等于目标值,返回false,等于就返回true。
代码实现
package com.algo.array;
public class Algo367 {
public boolean isPerfectSquare(int num) {
/**
* 初始化阶段,没有循环操作,时间复杂度为O(1)。
* 在 while 循环中,每次都将查找区间缩小一半,因此二分查找的时间复杂度为O(log n),其中n是输入数字的大小。
* 最终判断平方根是否符合要求的步骤,只是一次简单的比较操作,时间复杂度为O(1)。
* 因此,总体时间复杂度为O(log n),其中n是输入数字的大小。
*
* 分析空间复杂度:
* 这段代码只使用了有限的几个变量,没有使用额外的数据结构,因此空间复杂度为O(1),即常数级别的空间复杂度。
*/
//如果是0或1直接返回true
if(num==0|num==1){
return true;
}
int left = 1;//从1开始
int right = num;//因为一个数的平方不可能大于他本身
int index = 0;//用来存放取出来的索引即值
while (left<=right){
int mid = left + ((right-left)>>1);//定义中间变量
int temp = num/mid;//计算中间值的平方根
if(mid>temp){// 如果中间值的平方根小于中间值本身,说明平方根在左边
right = mid-1;
}else if(mid<temp){// 如果中间值的平方根大于中间值本身,说明平方根在右边
left = mid+1;
}else{// 如果中间值的平方根等于中间值本身,说明找到了平方根的整数部分
index = mid;//存放值
break;
}
}
if(index*index==num){//判断是否是其平方根
return true;
}
return false;
}
}