二分查找(持续更新)

例题1: 你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。

假设你有 n 个版本 [1, 2, …, n],你想找出导致之后所有版本出错的第一个错误的版本。

你可以通过调用 bool isBadVersion(version) 接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。

在这里插入图片描述
理解这道题是通过不断缩小边界然后当左右边界重合是就是答案,至于要不要取等号,这里推荐不要取等号尽管取等号也可以将题目解出

/* The isBadVersion API is defined in the parent class VersionControl.
      boolean isBadVersion(int version); */

public class Solution extends VersionControl {
    public int firstBadVersion(int n) {
    //    使用迭代超出了时间限制,那就二分查找
    int left=1;
    int right=n;
    while(left<right){
        // 首先取中间值这里为了防止溢出整数的范围,这里先将
        int mid =left+(right-left)/2;
        // 下面开始缩小范围
        if(isBadVersion(mid)){
            right=mid;
        }else{
            left=mid+1;
        }
    }
    return left;
    }
}

例题2:给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

请必须使用时间复杂度为 O(log n) 的算法。(力扣:35)

在这里插入图片描述

public class Solution {

    public int searchInsert(int[] nums, int target) {
        int len = nums.length;
        int left = 0;
        int right = len;
        // 在区间 nums[left..right] 里查找第 1 个大于等于 target 的元素的下标
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] < target){
                // 下一轮搜索的区间是 [mid + 1..right]
                left = mid + 1;
            } else {
                // 下一轮搜索的区间是 [left..mid]
                right = mid;
            }
        }
        return left;
    }
}


二分法的最新理解:
我们要判断进行边界收缩时,条件成立时移动的时左指针还是右指针,如果时移动做指针时,需要判断边界条件会不会卡死,就例如下面的代码:想象一下当只剩下4,5,如果int mid=(r+l)/2,那么当条件成立mid就永远为时会出现死循环,所以当移动左边界时,需要注意,移动右边界就不需要考虑了因为肯定会变小的

  while(l<r) {
//		  这里为什么要加一,因为如果一旦条件成同时有只剩下两个,也就是说mid就不再变化了,所以会导致死循环
		  int mid =(r+l+1)/2;
		  if(check(mid,arr,K)) {
			  l=mid;
		  }else {
			  r=mid-1;
		  }
	  }

例题3:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

package four;

import java.util.Scanner;

//这道题我本身是没有什么思路的,但是题解是使用2分法找出
//最大的边长
public class Four_2 {
  public static void main(String[] args) {
	  Scanner scan=new Scanner(System.in);
	  int N=scan.nextInt();
	  int K=scan.nextInt();
//	  创建一个2维数组保存每块巧克力的信息
	  int[][] arr=new int[N][2];
	  for(int i=0;i<N;i++) {
		  for(int j=0;j<2;j++) {
			  arr[i][j]=scan.nextInt();
		  }
	  }
//	  利用二分法来获取最大的边长
	  int l=1;
	  int r=100000;
	  while(l<r) {
//		  这里为什么要加一,因为如果一旦条件成同时有只剩下两个,也就是说mid就不再变化了,所以会导致死循环
		  int mid =(r+l+1)/2;
		  if(check(mid,arr,K)) {
			  l=mid;
		  }else {
			  r=mid-1;
		  }
	  }
	  System.out.print(l);
  }
//  创建一个函数以当前长度去分割巧克力是否可行
//  判断的条件就是分割后的巧克力总和能否大于人数
  public static boolean check(int maxlen,int[][] arr,int k) {
//	  创建一个变量计算总和
	  int sum=0;
	  for(int i=0;i<arr.length;i++) {
		  int count=(arr[i][0]/maxlen)*(arr[i][1]/maxlen);
		  sum=sum+count;
	  }
	  return sum>=k;
  }
}



例题:p1873 砍树问题
在这里插入图片描述
理解:这道题根之前的一道题一样都容易造成死循环,所以在求mid的时候需要时mid=(left+right+1)/2;而不是(left+right)/2

package 每天打卡;

import java.util.Arrays;
import java.util.Scanner;

//思路使用标签提示的二分法,对每个中点的值都进行计算
public class P1873 {
    public static void main(String[] args) {
    	Scanner scan=new Scanner(System.in);
    	int N=scan.nextInt();
    	int M=scan.nextInt();
    	int max=0;
//    	创建一个数组保存结果
    	int[] nums=new int[N];
    	for(int i=0;i<N;i++) {
    		nums[i]=scan.nextInt();
    		if(nums[i]>max) {
    			max=nums[i];
    		}
    	}
//    	创建两个指针
    	int left=0;
    	int right=max;
    	while(left<right) {
//注意上面这个循环的条件;当left==right,跳出循环也就找到了满足条件的最小高度
    		int mid =(left+right+1)>>1;   
//    		 如果当前切割的高度过于小
    		if(sum(nums,mid)>=M) {
//上面不能用mid=(left+right)/2! 因为当sum()为>+m,那么每次的mid都是left,结果left和right的值都没有改变,造成死循环
//换句话说,mid要偏向right,就是使切割树的高度变高
    			left=mid;
    		}else {
    			right=mid-1;
    		}
    	}
//    	输出结果
    	System.out.print(right);
    }
    
//    创建一个函数求以当前高度切割能获取的高度
//    必须使用一个长整型来保存结果,不然可能会因为int装不下而出错
    public static long sum(int[] nums,int mid) {
    	long total=0;
    	for(int i=0;i<nums.length;i++) {
    		if(nums[i]>mid) {
    			total=nums[i]-mid+total;
    		}	
    	}
    	return total;
    }
}

例题:木材加工
在这里插入图片描述
在这里插入图片描述


import java.util.Scanner;

public class Main{
  public static void main(String[] args) {
	  Scanner scan=new Scanner(System.in);
	  int n=scan.nextInt();
	  int k=scan.nextInt();
	  int[] nums=new int[n];
	  int max=0;
	  for(int i=0;i<n;i++) {
		  nums[i]=scan.nextInt();
		  if(nums[i]>max) {
			  max=nums[i];
		  }
	  }
	  int left=0;
	  int right=max;
//	  使用二分法来查找满足k的最大
	  while(left<right) {
		  int mid=(left+1+right)/2;
		  if(pd(nums,k,mid)) {
			  left=mid;
		  }else {
			  right=mid-1;
		  }
	  }
//	  输出结果
	  System.out.print(left);
  }
  public static boolean pd(int[] nums,int k,int cutlen) {
//	  创建一个变量保存切割的段数
	  int count=0;
	  for(int i=0;i<nums.length;i++) {
		  count=count+nums[i]/cutlen;
	  }
	  return count>=k;
  }
}

Last

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值