基本思想
首先确定答案在一个区间当中L到R。
通过中点判断答案在区间的某一边。
区间缩小,再次找中点。
70%的二分题目和单调性有关系
95%的题目 存在一种两段性的性质,左边成立,右边不成立。通过这个两段性的性质划分区间。
模板一
最后答案在右区间的左端点。
算mid下取整。
while(l<r){
int mid = l+r>>1;
if(check(mid))
r=mid;
else
l=mid+1;
}
return l;
模板二
最后的答案在左区间的右端点。
算mid上取整(避免死循环)
while(l<r){
int mid = l+r+1>>1;
if(check(mid)
l=mid;
else
r=mid-1;
}
return l;
流程
- 确定二分的边界l,r。定左右区间。
- 编写二分的代码框架。
- 设计一个check性质,性质要保证答案一定在性质的左右边界处且情况唯一。如果结果在左区间右端点用模板二,如果结果在右区间左端点用模板一
- 判断区间如何更新
- 如果更新方式写的是l=mid,r=mid-1,就在算mid的时候加一。
例 leetcode 69 X的平方根
按照二分流程分析
- 确定二分的边界,答案一定在0-x之间。
- 编写二分的代码框架
- check性质确定是。mid<=x/mid (防止溢出) 答案为左区间右端点,用模板二。
- 判断区间边界的情况,当条件满足时,mid包含在结果区间中。
- 更新情况是l=mid,r=mid-1。避免死循环,计算mid时要加一上取整。
public int mySqrt(int x) {
int l=0,r=x;
while(l<r){
int mid= (l+r+1)>>1;
if(mid<=x/mid){
l=mid;
}else
r=mid-1;
}
return l;
}
例 leetcode 35 搜索插入位置
这里使用二分模板三
public int searchInsert(int[] nums, int target) {
int start=0;
int end = nums.length-1;
if(target<nums[0])return 0;
if(target>nums[nums.length-1])return nums.length;
while(start+1<end){
int mid = start+(end-start)/2;
if(nums[mid]==target) return mid;
if(nums[mid]<=target){
start = mid;
}
else{
end=mid;
}
}
if(nums[start]==target) return start;
if(nums[end]==target) return end;
return start+1;
}
确定区间
确定条件
确定答案包含在右区间的左端点。使用模板一
if(nums.length==0||nums[nums.length-1]<target)return nums.length;
int start=0;
int end = nums.length-1;
while(start<end){
int mid= (start+end)>>1;
if(nums[mid]<target)
start=mid+1;
else
end=mid;
}
return start;
}
例 leetcode34 排序数组中查找元素的第一个位置和最后一个位置
二分的思想,先找到第一个位置,再找到最后一个位置。
通过两次二分,分别划分两个两段性性质。
分别代入模板一和模板二可以解决。
int []ans = {-1,-1};
public int[] searchRange(int[] nums, int target) {
if(nums.length==0)return ans;
int l=0,r=nums.length-1;
while(l<r){
int mid =(r+l)>>1;
if(nums[mid]>=target)//右区间左端点
r=mid;
else
l=mid+1;
}
if(nums[r]!=target)return ans;
ans[0]=r;
l=0;
r=nums.length-1;
while(l<r){
int mid=(r+l+1)>>1;
if(nums[mid]<=target)//左区间右端点
l=mid;
else
r=mid-1;
}
ans[1]=r;
return ans;
}
例 leetcode74 搜索二维矩阵
编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值。该矩阵具有如下特性:
每行中的整数从左到右按升序排列。
每行的第一个整数大于前一行的最后一个整数。
public boolean searchMatrix(int[][] matrix, int target) {
int m =matrix.length;
if(m==0)return false;
int n=matrix[0].length;
if(n==0)return false;
int start =0,end=m*n-1;
while(start<end){
int mid = start+(end-start)/2;
if(matrix[mid/n][mid%n]>=target)
end= mid;
else
start=mid+1;
}
if(matrix[start/n][start%n]==target) return true;
return false;
}
例 leetcode153. 寻找旋转排序数组中的最小值
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
请找出其中最小的元素。
你可以假设数组中不存在重复元素。
public int findMin(int[] nums) {
int l = 0,r=nums.length-1;
while(l<r){
int mid = l+r>>1;
if(nums[mid]>=nums[nums.length-1])
l=mid+1;
else
r=mid;
}
return nums[r];
}
例 leetcode 278. 第一个错误的版本
直接二分,注意有样例溢出。
public int firstBadVersion(int n) {
int l = 0,r=n;
while(l<r){
int mid= l+(r-l)/2;//避免溢出
if(isBadVersion(mid))
r=mid;
else
l=mid+1;
}
return l;
}
例 leetcode 162. 寻找峰值
这个题简单在于找到任何一个峰值即可。
最朴素的做法是遍历。
这里贴一个二分的代码:
public int findPeakElement(int[] nums) {
int l=0,r=nums.length-1;
while(l<r){
int mid=l+(r-l)/2;
if(nums[mid]<nums[mid+1])
l=mid+1;
else
r=mid;
}
return l;
}
例 leetcode275.H指数II
给定一位研究者论文被引用次数的数组(被引用次数是非负整数),数组已经按照升序排列。编写一个方法,计算出研究者的 h 指数。
h 指数的定义: “h 代表“高引用次数”(high citations),一名科研人员的 h 指数是指他(她)的 (N 篇论文中)至多有 h 篇论文分别被引用了至少 h 次。(其余的 N - h 篇论文每篇被引用次数不多于 h 次。)"
public int hIndex(int[] citations) {
int n=citations.length;
if(n==0)return 0;
int l=0,r=n-1;
while(l<r){
int mid =l+(r-l)/2;
if(citations[mid]>=n-mid)
r=mid;
else
l=mid+1;
}
if(citations[l]==0)
return 0;
return n-l;
}