知识融汇
- 实际上二分法也是双指针算法,属于相向双指针,使用left指向初始节点,right指向最后节点。
使用条件
-
数组有序(30%-40%的概率是二分)
-
复杂度比
O(n)
小,大概为O(logn)
的复杂度 -
可以在数组中找到一个分割位置,使得一半满足条件继续搜索,另一半不满足条件。
-
找一个最大值/最小值使得某个条件被满足。
Note:此二分法模板使用的是双指针实现,如果一个题目有序,也可以使用此模板的双指针法进行求解。
算法万能模板
非递归模板
模板重点记住两点,万能while中条件的判断,和while循环外单独判断。一般建议使用非递归模板,逻辑清晰容易发现错误,递归不太直观。
public int binarySearch_f(int[] nums, int target) {
if (nums.length == 0 || nums == null) {
return -1;
}
int start = 0;
int end = nums.length - 1;
//重点:start+1<end ,此时会少判断一次需要单独拿出来判断,
//避免边界条件出现死循环,这也是此模板的灵魂,精髓
while (start+1<end){
int mid = start + (end-start)/2;
if(nums[mid]==target){
return mid;
} else if (nums[mid]>target) {
end = mid; // 不用管要不要mid-1 或者 mid+1 ,全部写成 mid
} else{
start = mid;
}
}
// 重点:精髓,因为循环中跳过了中间相邻两个元素的判断,因此需要在外面单独判断
// 此时经过while循环,start 与 end 相邻,即 start+1==end
if(nums[start]==target){
return start;
}
if(nums[end]==target){
return end;
}
return -1;
}
递归模板
public int binarySearch_f2(int[] nums,int start , int end , int target) {
if (nums.length == 0 || nums == null) {
return -1;
}
int middle = start + (end-start)/2;
//边界条件的判断
if(start+1==end){
if(nums[start]==target){
return start;
}else if(nums[end]==target){
return end;
}else {
return -1;
}
}
if(nums[middle]==target){
return middle;
}
if(nums[middle]<target){
return binarySearch_f2(nums,middle,end,target);
}else {
return binarySearch_f2(nums,start,middle,target);
}
}
LeetCode练习
- 在排序数组中查找元素的第一个和最后一个位置[力扣]
参考图1,请写代码实现二分查找,分别查数组中第一次target出现的下标和最后一次出现的下标,当解决这两个问题,其他问题只需要在此基础上简单变换即可。
-
第一次出现的位置
public int binarySearch_first(int[] nums, int target) { if (nums.length == 0 || nums == null) { return -1; } int start = 0; int end = nums.length - 1; while (start+1<end){ int mid = start + (end-start)/2; if(nums[mid]==target){ end = mid; // 当找到了一个并不能直接返回,而是将其作为右边界,包含关系 } else if (nums[mid]>target) { end = mid; // 不用管要不要mid-1 或者 mid+1 ,全部写成 mid } else{ start = mid; } } // 注意 到底是两个if还是else if if(nums[start]==target){ first = start; }else if(nums[end]==target){ first = end; } return first;
-
最后一次出现的位置
public int binarySearch_last(int[] nums, int target) { if (nums.length == 0 || nums == null) { return -1; } int start = 0; int end = nums.length - 1; while (start+1<end){ int mid = start + (end-start)/2; if(nums[mid]==target){ start = mid; // 当找到了一个并不能直接返回,而是将其作为左边界,包含关系 } else if (nums[mid]>target) { end = mid; // 不用管要不要mid-1 或者 mid+1 ,全部写成 mid } else{ start = mid; } } //注意这两个边界点的判断顺序,return 可以两个if if(nums[end]==target){ return end; } if(nums[start]==target){ return start; } return -1; }
-
- 搜索旋转排序数组[力扣]
public int search(int[] nums, int target) { int left = 0; int right = nums.length-1; while(left+1<right){ int middle = left + (right-left)/2; if(nums[middle]>nums[left]){ //左侧有序 if(target>=nums[left] && target<=nums[middle]){ right = middle; }else{ left = middle; } } if(nums[middle]<nums[right]){ //右侧有序 if(target>=nums[middle] && target<=nums[right]){ left = middle; }else{ right = middle; } } } if(nums[left]==target){ return left; } if(nums[right]==target){ return right; } return -1; }
-
- 寻找峰值[力扣]
public int findPeakElement(int[] nums) { //此题有两点第一边界是负无穷,因此,只要某一边在爬坡就一定会有山峰,因为就算一致上升,那么到了边界就是悬崖,即最后一个元素是山峰 //因此,这道题的二分技巧是丢弃下降的一半,寻找上升的一半 int left = 0; int right = nums.length; while (left+1<right){ int middle = left+(right-left)/2; if(nums[middle]<nums[middle+1]){ left = middle; }else{ right = middle; } } if(nums[left]>nums[right]){ return left; }else { return right; } }
-
- 寻找旋转排序数组中的最小值[力扣]
public int findMin(int[] nums) { int left=0; int right = nums.length-1; if(nums[left]<nums[right]){ //如果数组有序 return nums[left]; } while(left+1<right){ int mid = left + (right-left)/2; if(nums[mid]>nums[left]){ //左边有序,往右边找 left = mid; } else if (nums[mid] < nums[right]) { //右边有序,往左边找 right = mid; } } return Math.min(nums[left],nums[right]); }
-
- 有效三角形的个数[力扣]
public int last_target(int[] nums ,int start, int end , int target){ //从nums的start到end中寻找最后一个满足target的下标 while(start+1<end){ int mid = (start+end)/2; if(nums[mid]<target){ start = mid; }else { end = mid; } } if(end<nums.length && nums[end]<target){ return end; } if(start<nums.length && nums[start]<target){ return start; } return -1; } public int triangleNumber(int[] nums) { //解法:两短边之和大于第三边,先排序,再查找 Arrays.sort(nums); int count = 0; for (int i = 0; i < nums.length; i++) { for (int j = i+1; j <nums.length ; j++) { // i j 指向的是两个短边,从后面的数中搜索第一个满足条件的下标 int fdx = last_target(nums,j+1,nums.length-1,nums[i]+nums[j]); if(fdx!=-1){ count += (fdx-j); } } } return count; }