1、二分法模板(1)
*主要的难点是边界条件的判断,下面一个代码示例是一个最基础的二分法模板,简单题目与部分中等题目比较适合275. H 指数 II - 力扣(LeetCode) (leetcode-cn.com)
class Solution {
public int searchInsert(int[] nums, int target) {
int h= nums.length-1;
int l =0;
{
int m = l + (h-l)/2;
if(target <= nums[m])
h=m;
else
l=m+1;
}
return target <= nums[h] ? h:h+1;
}
}
2、二分法模板(2)
*比较复杂的二分法模板,对应与中等难度的题目,相较于第一个模板,这个模板就比较复杂一点:例如leetcode中的第1482道1482. 制作 m 束花所需的最少天数 - 力扣(LeetCode) (leetcode-cn.com)以及274与275道,需要对mid处的数据进行一个判断,依旧必须考虑边界值。
class Solution {
public int minDays(int[] bloomDay, int m, int k) {
if(m*k>bloomDay.length)
return -1;
int low = Arrays.stream(bloomDay).min().getAsInt();
int high = Arrays.stream(bloomDay).max().getAsInt();
while(low < high)
{
int mid = low+(high-low)/2;
if(check(bloomDay,m,k,mid))
high = mid;
else
low = mid + 1;
}
return high;
}
boolean check(int[] bloomDay,int m,int k,int days)
{
int flower = 0;
int mf = 0;
for(int i=0;i<bloomDay.length;i++)
{
if(bloomDay[i]<=days)
{
flower++;
if(flower==k)
{
mf++;
flower =0;
}
}
else flower = 0;
}
return mf>=m;
}
}
二分法再学习
1、二分法想起来容易,实现起来缺很复杂,经常会出现边界判断出错的问题。今天我又重新学习了一下34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)这道题目
解题思路:可以分为两部分,第一步寻找左边界,具体做法是当nums[mid] >= target时,右边指针减一,否则左边指针加一,这样如果存在目标值的话,目标值的左边界就是右指针。第二步,寻找右边界,做法与左边界相似,具体解法如下:
public int[] searchRange(int[] nums, int target) {
int[] ans = new int[]{-1, -1};
if(nums.length == 0 || nums[0] > target || nums[nums.length - 1] < target)
return ans;
int l = 0, r = nums.length - 1;
//寻找左边界
while(l <= r){
int mid = l + (r - l) / 2;
if(nums[mid] >= target)
r = mid - 1;
else
l = mid + 1;
}
ans[0] = l;
//寻找右边界
l = 0; r = nums.length - 1;
while(l <= r){
int mid = l + (r - l) / 2;
if(nums[mid] <= target)
l = mid + 1;
else
r = mid - 1;
}
ans[1] = r;
if(ans[0] > ans[1])
return new int[]{-1, -1};
return ans;
}
2、153. 寻找旋转排序数组中的最小值 - 力扣(LeetCode)
对于如下图所示的数组,我们要清楚数组的后半部分的最大值比前半部分的最小值要小,换句话说,后半部分小于前半部分,这样的话,我们就可以使用二分法进行判断进行查找了。代码如下:这里需要注意的是,nums[mid] > nums[nums.length - 1]时 l = mid + 1;这是因为此时最小值一定不是mid;而对于nums[mid] < nums[nums.length - 1]的情况就是 mid 有可能是最小值,那么就不能直接 mid - 1,mid - 1 可能会错过最小值。
/*方法一*/
public int findMin(int[] nums) {
int l = 0, r = nums.length - 1;
while (l < r){
int mid = l + (r - l) / 2;
if(nums[mid] > nums[nums.length - 1])
l = mid + 1;
else r = mid;
}
return nums[l];
}
/*方法二*/
public int findMin(int[] nums) {
int l = 0, r = nums.length - 1;
while (l <= r){
int mid = l + (r - l) / 2;
if(nums[mid] > nums[nums.length - 1])
l = mid + 1;
else r = mid - 1;
}
return nums[l];
}
3、做完这道还有一道,33. 搜索旋转排序数组 - 力扣(LeetCode)
对与上图这种元素也可以使用二分查找找到最小值,具体做法在代码注释里面有
public static int search(int[] nums, int target) {
int n = nums.length;
if(n == 0)
return -1;
int l = 0, r = n - 1;
//利用二分法寻找直接判断,类似于找旋转点
while(l <= r){
int mid = l + (r - l) / 2;
if(nums[mid] == target)
return mid;
//中间点位于前半部分
if(nums[mid] > nums[r]){
//判断目标值是否位于左端点与中间点之间
if(nums[mid] > target && target >= nums[l])
r = mid - 1;
else
l = mid + 1;
}
//中间点位于后半部分
else{
//判断目标值是否位于中间点与右端点之间
if(nums[mid] < target && target <= nums[r])
l = mid + 1;
else
r = mid - 1;
}
}
return -1;
}