二分查找总结与最佳实践(LeetCode 975 + LeetCode 878 + LeetCode 778 )

(一) 使用场景

  1. 通常在有序的数字中,找到某个满足一定条件的数
  2. 多次问询题
  3. 需要多次遍历且数据量极大

(二)基本流程

  1. 确定边界(low, high)值
  2. 确定边界移动条件,一定只写一边移动(low=mid high=mid-1) 或者 (low=mid+1 high=mid)
  3. 循环外返回结果 (划重点)

当有多个值满足时,返回是最左边那个还是最右边那个?

def bisect_left(nums, target):  # 寻找最左边的那个数,更新哪一边就是找对的最那边的数
    l, r = 0, len(nums) - 1
    while l < r:
        m = (l + r) // 2
        if nums[m] < target:
            l = m + 1
        else:
            r = m
    return l if nums[l] == target else -1

def bisect_right(nums, target): # 寻找最右边的那个数
    l, r = 0, len(nums) - 1
    while l < r:
        m = (l + r) // 2 + 1
        if nums[m] > target:
            r = m - 1
        else:
            l = m
    return l if nums[l] == target else -1

return [bisect_left(nums, target), bisect_right(nums, target)]

(三)简单例题

给定一个有序数组nums和一个整数target,判断target是否存在于nums中?

  • 这种写法有陷入死循环的风险!还需改进
  • (需要按照题意来确定到底是更新左边还是更新右边啊)
 public boolean binary_search(int[] nums , int target) {  
        int low = 0;
        int high = nums.length-1;
        while(low<high){
            int mid = low + (high-low)/2;    //标准写法,避免int相加越界
            if(nums[mid]<target) low = mid+1;
            else  high = mid;  //  if(nums[mid]>=target])
        }
        return nums[low]==target;  //循环外返回结果
    }
  • 改进为:
 public boolean binary_search(int[] nums , int target) {  
        int low = 0;
        int high = nums.length-1;
        while(low<high-1){
            int mid = low + (high-low)/2;    //标准写法,避免int相加越界
            if(nums[mid]<target) low = mid+1;
            else  high = mid;  //  if(nums[mid]>=target])
        }
        return nums[low]==target or nums[high]==target;  //循环外返回结果,这里多判断一次
    }

(四)精选例题

(1)Leetcode 875 KoKo Eating Bananas

Koko喜欢吃香蕉,现存在N盘香蕉,每盘上有piles[i]根香蕉,总共有H个小时可以让koko进食
Koko每小时可以吃K根香蕉,但是同一小时只能选择吃一盘香蕉。
计算最小的K值,使得H小时内koko能将所有香蕉吃完。

输入: piles = [3, 6, 7, 8], H = 8
输出: K = 4

public class Eating_Bananas_875 {
    public int minEatingSpeed(int[] piles, int H) {
        int low = 1 , high = piles[0];
        for(int i=1;i<piles.length;i++){
            if(piles[i]>high) high = piles[i];
        }
        while(low<high){ 
            int mid = low + (high-low)/2;
            if(check(piles,mid,H)) high = mid;
            else low = mid+1;
        }
        return low;//循环外返回
    }
    
    public boolean check(int[] piles , int speed,int H){  //最优解是low与high相遇的时候才是
        int time = 0;
        for(int i=0;i<piles.length;i++){
            if(piles[i]%speed==0) time+=piles[i]/speed;  //每次吃一盘后剩余时间不能去吃其他盘
            else time += piles[i]/speed+1;
        }
        return time<=H;
    }
}

(2) Leetcode 878 Nth Magical Number

定义一个整数是神奇的,当且仅当这个能被A或者B整除
给定A,B,返回第N个神奇数字,答案可能非常大,需要mod 10^9+7
1<=N<=10^9, 2<=A,B<=40000

输入: N = 4,A = 2,B = 3
输出: 6
解释:2,3,4,6

public class Nth_Magical_Number_878 {
    public int nthMagicalNumber(int N, int A, int B) {
        long low=2L,high=100000000000000L; //上界到底怎么算呢?随便找个最大值试一下
        while(low<high){
            long mid = low + (high-low)/2;
            if(check(mid,N,A,B)) low = mid+1;
            else high = mid;
        }
        return (int)(low%1000000007); //少加了括号导致WA了
    }
    
    // 检查Mid是第多少个神奇数
    // mid / A 是A的倍数,也就是Mid之前有多少个满足能被A整除的数
    // mid / B 同理,但是中间可能有既是A的倍数也是B的倍数的,所以要把这些重复值删除
    public boolean check(long mid,int N,int A,int B){
        long magicals = mid/A + mid/B - mid/lcm(A,B);
        return magicals < N ;
    }
    
    public int gcd(int a,int b){ //最大公约数
        return b==0?a:gcd(b,a%b);
    }
    
    public int lcm(int a,int b){ //最小公倍数
        return a*b/gcd(a,b);
    }
}

(3) Leetcode 778 Swim in Rising Water

给定一个NxN的网格grid,请你找到一条道路,从(0,0)出发一直走到(n-1,n-1),使得这条路上的最大值最小。并返回这个值。

class Solution {
    public int swimInWater(int[][] grid) {
        if(grid==null || grid.length==0 || grid[0].length==0) return 0;
        int len = grid[0].length;
        int low=grid[0][0],high=len*len-1;
        while(low<high){
            int mid = low + (high-low)/2;
            boolean[][] visited = new boolean[len+1][len+1];
            if(DFS(mid,0,0,grid,visited)==1) high = mid;
            else low = mid+1;
        } 
        return low;
    }
    
    public int DFS(int depth,int x,int y,int[][] grid,boolean[][] visited) //在点(x,y)出发能找到一条路径,路径上的值都小于mid
    {
        int n = grid[0].length;  
        if(x<0 || x>=n || y<0 || y>=n) 
            return 0;
        if(visited[x][y] || grid[x][y]>depth)  //空指针异常,visited数组有问题
            return 0;
         if(x==n-1&&y==n-1)
            return 1;
        int[][] DIRS = new int[][]{{1, 0}, {0, 1}, {-1, 0}, {0, -1}};
        visited[x][y]=true;  //标记(x,y)已经访问过了
        for(int[] dir:DIRS){
            int newX = x + dir[0];
            int newY = y + dir[1];
            if(DFS(depth,newX,newY,grid,visited)==1) 
                return 1;
        }
        return 0;
    }     
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值