二分专题讲解-看完之后必须会二分

简介: 二分专题讲解-看完之后必须会二分

二分专题讲解

二分法查找某个有序数组中的指定数字可以达到log(n)的时间复杂度。

运用二分法的前提一定是针对某个有序集合去进行操作,如果该集合非有序的,一定是不能进行二分操作的。

在目前的刷题单中,遇到用二分的场景有两大类:

1. 二分查找有序数组的某个值

在这种大类下,又分为三种情况:

  • 查找有序数组某个值(数组中该值有且仅出现一次)

LeetCode704 二分查找

class Solution {
    public int search(int[] nums, int target) {
        if (nums == null || nums.length == 0) {
            return -1;
        }
        int n = nums.length;
        // 如果传进来的nums数组已经有序了,就不需要进行这一步排序操作了。反之,则需要
        // Arrays.sort(nums);
        int l = 0;
        int r = n - 1;
        while (l <= r) {
            // 防止溢出
           int mid = l + (r - l) / 2;
           if (nums[mid] == target) {
               return mid;
           } else if (nums[mid] < target) {
               l = mid + 1;
           } else {
               r = mid - 1;
           }
        }
        return -1;
    }
}
  • 查找有序数组第一次出现的某个值(数组中该值出现的超过1 次),左区间

LeetCode 744. 寻找比目标字母大的最小字母

class Solution {
    public char nextGreatestLetter(char[] letters, char target) {
        int length = letters.length;
        if (target >= letters[length - 1]) {
            return letters[0];
        }
        int low = 0, high = length - 1;
        while (low < high) {
            int mid = (high - low) / 2 + low;
            if (letters[mid] > target) {
                high = mid;
            } else {
                low = mid + 1;
            }
        }
        return letters[low];
    }
}
  • 查找有序数组最后一次出现的某个值(数组中该值出现的超过1次),右区间

LeetCode 34. 在排序数组中查找元素的第一个和最后一个位置

class Solution {
    public int[] searchRange(int[] nums, int target) {
        int l = binarySearchLeft(nums, target);
        int r = binarySearchRight(nums, target);
        return new int[]{l,r};
    }
    /**
     * 
     * @param nums 传入一个有序数组
     * @param target 需要在数组中查找出现的目标值中最左边的一个目标值
     * @return 如果target在nums中存在则返回第一次出现的下标位置,否则返回-1
     */
    public int binarySearchLeft(int[] nums, int target) {
        if (nums == null || nums.length == 0) {
            return -1;
        }
        int n = nums.length;
        // 如果传进来的nums数组已经有序了,就不需要进行这一步排序操作了。反之,则需要
        // Arrays.sort(nums);
        int l = 0;
        int r = n - 1;
        while (l < r) {
            int mid = l + (r - l) / 2;
            if (nums[mid] > target) {
                r = mid - 1;
            } else if (nums[mid] < target){
                l = mid + 1;
            } else {
                r = mid;
            }
        }
        // 确认是否存在:如果l不在合法范围内或者nums[l] != target,那么直接返回-1,否则返回l
        return (l >= n) ? -1 : (nums[l] == target ? l : -1);
    }
    /**
     *
     * @param nums 传入一个有序数组
     * @param target 需要在数组中查找出现的目标值中最右边的一个目标值
     * @return 如果target在nums中存在则返回最后一次出现的下标位置,否则返回-1
     */
    public int binarySearchRight(int[] nums, int target) {
        if (nums == null || nums.length == 0) {
            return -1;
        }
        int n = nums.length;
        // 如果传进来的nums数组已经有序了,就不需要进行这一步排序操作了。反之,则需要
        // Arrays.sort(nums);
        int l = 0;
        int r = n - 1;
        while (l < r) {
            int mid = l + (r - l + 1) / 2;
            if (nums[mid] > target) {
                r = mid - 1;
            } else if (nums[mid] < target){
                l = mid + 1;
            } else {
                l = mid;
            }
        }
        // 确认是否存在:如果l不在合法范围内或者nums[l] != target,那么直接返回-1,否则返回l
        return (l >= n) ? -1 : (nums[l] == target ? l : -1);
    }
}

综上所述,我这里总结了二分查找数组中某个目标值的下标的三类情况分别对应的代码:

public class Main {
    public static void main(String[] args) {
       Main main = new Main();
       int[] nums = {0,1,1,1,1,4,6,9,10};
       int[] arr = {0,1,4,6,9,10};
       int[] arr2 = {2,2};
        int index = main.binarySearch(arr,4);
        int indexLeft = main.binarySearchLeft(nums, 1);
        int indexRight = main.binarySearchRight(arr2, 3);
        System.out.println(index);
        System.out.println(indexLeft);
        System.out.println(indexRight);
    }
    /**
     *
     * @param nums 传入一个有序数组,且需要保证「nums中target要么仅仅出现一次,要么没有出现」
     * @param target 需要在数组中查找出现的目标值中
     * @return 如果target在nums中存在则返回出现的下标位置,否则返回-1
     */
    public int binarySearch(int[] nums, int target) {
        if (nums == null || nums.length == 0) {
            return -1;
        }
        int n = nums.length;
        // 如果传进来的nums数组已经有序了,就不需要进行这一步排序操作了。反之,则需要
        // Arrays.sort(nums);
        int l = 0;
        int r = n - 1;
        while (l < r) {
           int mid = l + (r - l) / 2;
           if (nums[mid] == target) {
               return mid;
           } else if (nums[mid] < target) {
               l = mid + 1;
           } else {
               r = mid - 1;
           }
        }
        return -1;
    }
    /**
     *
     * @param nums 传入一个有序数组
     * @param target 需要在数组中查找出现的目标值中最左边的一个目标值
     * @return 如果target在nums中存在则返回第一次出现的下标位置,否则返回-1
     */
    public int binarySearchLeft(int[] nums, int target) {
        if (nums == null || nums.length == 0) {
            return -1;
        }
        int n = nums.length;
        // 如果传进来的nums数组已经有序了,就不需要进行这一步排序操作了。反之,则需要
        // Arrays.sort(nums);
        int l = 0;
        int r = n - 1;
        while (l < r) {
            int mid = l + (r - l) / 2;
            if (nums[mid] > target) {
                r = mid - 1;
            } else if (nums[mid] < target){
                l = mid + 1;
            } else {
                r = mid;
            }
        }
        // 确认是否存在:如果l不在合法范围内或者nums[l] != target,那么直接返回-1,否则返回l
        return (l >= n) ? -1 : (nums[l] == target ? l : -1);
    }
    /**
     *
     * @param nums 传入一个有序数组
     * @param target 需要在数组中查找出现的目标值中最右边的一个目标值
     * @return 如果target在nums中存在则返回最后一次出现的下标位置,否则返回-1
     */
    public int binarySearchRight(int[] nums, int target) {
        if (nums == null || nums.length == 0) {
            return -1;
        }
        int n = nums.length;
        // 如果传进来的nums数组已经有序了,就不需要进行这一步排序操作了。反之,则需要
        // Arrays.sort(nums);
        int l = 0;
        int r = n - 1;
        while (l < r) {
            int mid = l + (r - l + 1) / 2;
            if (nums[mid] > target) {
                r = mid - 1;
            } else if (nums[mid] < target){
                l = mid + 1;
            } else {
                l = mid;
            }
        }
        // 确认是否存在:如果l不在合法范围内或者nums[l] != target,那么直接返回-1,否则返回l
        return (l >= n) ? -1 : (nums[l] == target ? l : -1);
    }
}

2. 二分查找限定范围内,操作限定次数下的最值问题

可以先看一道经典的题

LeetCode 875. 爱吃香蕉的珂珂

珂珂喜欢吃香蕉。这里有 n 堆香蕉,第 i 堆中有 piles[i] 根香蕉。警卫已经离开了,将在 h 小时后回来。

珂珂可以决定她吃香蕉的速度 k (单位:根/小时)。每个小时,她将会选择一堆香蕉,从中吃掉 k 根。如果这堆香蕉少于 k 根,她将吃掉这堆的所有香蕉,然后这一小时内不会再吃更多的香蕉。

珂珂喜欢慢慢吃,但仍然想在警卫回来前吃掉所有的香蕉。

返回她可以在 h 小时内吃掉所有香蕉的最小速度 k(k 为整数)。

示例 1:

输入:piles = [3,6,7,11], h = 8

输出:4

示例 2:

输入:piles = [30,11,23,4,20], h = 5

输出:30

示例 3:

输入:piles = [30,11,23,4,20], h = 6

输出:23

提示:

image.png

class Solution {
    public int minEatingSpeed(int[] piles, int h) {
        int l = 1;
        int r = (int) (1e9);
        while (l <= r) {
            int mid = l + (r - l) / 2;
            if (check(piles, mid, h)) {
                // 如果说超过了指定的时间h,说明吃香蕉的速度还要再快一些,也就是l需要增大
                l = mid + 1;
            } else {
                // 如果说没超过指定的时间h,说明吃香蕉的速度还要再慢一些,也就是r需要减小
                r = mid - 1;
            }
        }
        // 要返回的是 h 小时内吃掉所有香蕉的最小速度 k,也就是如果k再大,那就要超过h。因此返回的是左区间的l
        return l;
    }
    public boolean check(int[] piles ,int k, int h) {
        int cnt = 0;
        for (int pile : piles) {
            cnt += (1 + (pile - 1) / k);
            // 向上取整和下面效果一样
            // if (pile % k == 0) {
            //     cnt += pile / k;
            // } else {
            //     cnt += 1 + pile / k;
            // }
        }
        return cnt > h;
    }
}

还可以做下面这些经典的二分题

https://www.luogu.com.cn/training/111#problems

P1182 数列分段 Section II

题目描述

image.png

输出格式

一个正整数,即每段和最大值最小为多少。

样例

样例输入
5 3
4 2 4 5 1
样例输出
6

提示

image.png

import java.io.*;
import java.util.*;
public class Main {
    public static boolean check(int mid, int[] arr, int m) {
        long sum = 0;
        int cnt = 0;
        int n = arr.length;
        for (int i = 0 ; i < n; i++) {
            if (sum + arr[i] <= mid) {
                sum += arr[i];
            } else {
                cnt++;
                sum = arr[i];
            }
        }
        return cnt >= m;
    }
    public static void main(String[] args) throws IOException {
        StreamTokenizer st = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
        st.nextToken();
        int n = (int)st.nval;
        st.nextToken();
        int m = (int)st.nval;
        int[] arr = new int[n];
        int l = 0;
        int r = 0;
        for (int i = 0; i < n; i++) {
            st.nextToken();
            arr[i] = (int)st.nval;
            l = Math.max(arr[i], l);
            r += arr[i];
        }
        while (l <= r) {
            int mid = (l + r) >> 1;
            if (check(mid,arr,m)) {
                l = mid + 1;
            } else {
                r = mid - 1;
            }
        }
        System.out.println(l);
    }
}

总结一下:

对于二分查找限定范围内,操作次数限定下的最值问题。

image.png

目录
打赏
0
0
0
0
23
分享
相关文章
幻兽帕鲁服务器清档教程
介绍了Linux(ubuntu)和Windows平台下清除服务器存档的教程。适用于计算巢部署和已有ECS部署。
6228 0
Ubuntu无法找到add-apt-repository问题的解决方法
Ubuntu无法找到add-apt-repository问题的解决方法
327 1
Ganos实时热力聚合查询能力解析与最佳实践
Ganos是由阿里云数据库产品事业部与飞天实验室共同研发的新一代云原生位置智能引擎,集成于PolarDB-PG、Lindorm、AnalyticDB-PG和RDS-PG等核心产品中。Ganos拥有十大核心引擎,涵盖几何、栅格、轨迹等多种数据处理能力,实现了多模多态数据的一体化存储、查询与分析。本文重点介绍了Ganos的热力瓦片(HMT)技术,通过实时热力聚合查询与动态输出热力瓦片,无需预处理即可实现大规模数据秒级聚合与渲染,适用于交通、城市管理、共享出行等多个领域。HMT相比传统网格聚合技术具有高效、易用的优势,并已在多个真实场景中验证其卓越性能。
178 0
我写个HarmonyOS Next版本的微信聊天01
我写个HarmonyOS Next版本的微信聊天01
195 1
我写个HarmonyOS Next版本的微信聊天01
手搭手入门Spring boot+Mybatis+达梦数据库(国产数据库)
手搭手入门Spring boot+Mybatis+达梦数据库(国产数据库)
QGS
966 0
"二叉树的性质与推导及常见习题整理 "
这篇内容介绍了二叉树的一些性质及其推导。
413 0
工单系统大揭秘!选择工单系统需注意的关键因素!
这篇内容介绍了工单系统的种类和选择指南。主要类型包括IT工单系统、客户服务工单管理系统、设备维护工单管理系统和全渠道工单系统。选择合适的工单系统需考虑功能需求、企业预算、易用性、系统稳定性、售后服务和技术安全。推荐了Zoho Desk作为好用的工单系统选项,它提供专业服务和免费试用。
214 1
如何优化前端性能:一种全面的策略探讨
本文将介绍一种全面的前端性能优化策略,包括代码优化、资源加载、网络请求以及页面渲染等方面的技术手段和实践经验。通过综合运用这些方法,可以有效提升网站或应用的性能表现,提升用户体验和页面加载速度。
如何在云服务器使用docker快速部署jupyter web服务器(Nginx+docker+jupyter+tensorflow)
如何在云服务器使用docker快速部署jupyter web服务器(Nginx+docker+jupyter+tensorflow)
335 0
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等

登录插画

登录以查看您的控制台资源

管理云资源
状态一览
快捷访问