【秋招刷题打卡】Day02-二分系列之-二分查找

Day02-二分系列之-二分查找

前言

给大家推荐一下咱们的 陪伴打卡小屋 知识星球啦,详细介绍 =>笔试刷题陪伴小屋-打卡赢价值丰厚奖励 <=

⏰小屋将在每日上午发放打卡题目,包括:

  • 一道该算法的模版题 (主要以力扣,牛客,acwing等其他OJ网站的题目作为模版)
  • 一道该算法的应用题(主要以往期互联网大厂 笔试真题 的形式出现,评测在咱们的 笔试突围OJ

在这里插入图片描述

小屋day02

我们预计花三天的时间来介绍和巩固二分的题目,其中包括

  • 二分查找
  • 二分答案
  • 二分最大化最小值/最小化最大值

其中笔试常考的为后两类,今年春招中出现了不下 10 次。

引言

举个二分的例子

比如有一个有序单调不减的数组 a r r arr arr,以及一个目标值 X X X ,要求在 a r r arr arr 中找到第一个 ≥ X \ge X X 的数。

做法:每次考察数组当前部分的中间元素,如果中间元素刚好是要找的,就结束搜索过程;如果中间元素小于所查找的值,那么左侧的只会更小,不会有所查找的元素,只需到右侧查找;如果中间元素大于所查找的值同理,只需到左侧查找。

通过二分搜索能够有效的帮原本 O ( n ) O(n) O(n) 遍历数组的时间复杂度降为 O log ⁡ ( n ) O \log(n) Olog(n)

当然二分能做的事远远不止如此,一个题目,如果一个区间具有单调性质,那么一定可以二分,但是如果说这道题目没有单调性质,而是具有某种区间性质的话,我们同样可以使用二分,二分的题目,往往会出现最大值最小值, 或者单调性质。题目如果出现最大的最小值最小的最大值的类似的字眼,一般是可以使用二分来解决。

✨ 以下提供一个,本人长期使用的一个比较好用的手写二分模版

二分模版

二分模板一共有两个,分别适用于不同情况,使用时只需修改check函数即可。
算法思路:假设目标值在闭区间 [l, r] 中, 每次将区间长度缩小一半,当 l = r 时,我们就找到了目标值。

版本1

当我们将区间[l, r]划分成[l, mid][mid + 1, r]时,其更新操作是r = mid或者l = mid + 1;,计算mid时不需要加 1。

  • CPP

    int bsearch_1(int l, int r)
    {// l 为左端点,r 为右端点,都是闭区间// 使用时只需修改check函数即可while (l < r){int mid = l + r >> 1;if (check(mid)) r = mid; // check函数代表你需要进行的判断操作// 最终的答案会满足check条件else l = mid + 1; // 一定是这么写 不用多想}return l; // 此时的 l 为答案 (l == r)
    }
    
  • Java

    public int bsearch_1(int l, int r) {// l 为左端点,r 为右端点,都是闭区间// 使用时只需修改check函数即可while (l < r) {int mid = (l + r) >> 1;if (check(mid)) {r = mid; // check函数代表你需要进行的判断操作// 最终的答案会满足check条件} else {l = mid + 1; // 一定是这么写 不用多想}}return l; // 此时的 l 为答案 (l == r)
    }
    
  • Python

    def bsearch_1(l, r):# l 为左端点,r 为右端点,都是闭区间# 使用时只需修改check函数即可while l < r:mid = (l + r) // 2if check(mid):r = mid # check函数代表你需要进行的判断操作# 最终的答案会满足check条件else:l = mid + 1 # 一定是这么写 不用多想return l # 此时的 l 为答案 (l == r)
    
版本2

当我们将区间[l, r]划分成[l, mid - 1][mid, r]时,其更新操作是r = mid - 1或者l = mid;,此时为了防止死循环,计算mid时需要加1。

  • CPP

    int bsearch_2(int l, int r)
    {// l 为左端点,r 为右端点,都是闭区间// 使用时只需修改check函数即可while (l < r){int mid = l + r + 1 >> 1; // 注意这里要多加 1if (check(mid)) l = mid; // check函数代表你需要进行的判断操作// 最终的答案会满足check条件else r = mid - 1; // 一定是这么写 不用多想}return l; // 此时的 l 为答案 (l == r)
    }
    
  • Java

    public int bsearch_2(int l, int r) {// l 为左端点,r 为右端点,都是闭区间// 使用时只需修改check函数即可while (l < r) {int mid = (l + r + 1) >> 1;// 注意这里要多加 1if (check(mid)) {l = mid; // check函数代表你需要进行的判断操作// 最终的答案会满足check条件} else {r = mid - 1; // 一定是这么写 不用多想}}return l; // 此时的 l 为答案 (l == r)
    }
    
  • Python

    def bsearch_2(l, r):# l 为左端点,r 为右端点,都是闭区间# 使用时只需修改check函数即可while l < r:mid = (l + r + 1) // 2 # 注意这里要多加 1if check(mid):l = mid # check函数代表你需要进行的判断操作# 最终的答案会满足check条件else:r = mid - 1 # 一定是这么写 不用多想return l # 此时的 l 为答案 (l == r)
    
什么时候使用版本1 or 2?

清隆这边给大家总结了一下:

  • 如果在 if(check()) 判断之后需要 跟新(左移) 右端点的,用 版本1
  • 反之,如果是需要 跟新(右移) 左端点的,用 版本2

接来下我们看看模版如何运用

🎀 模版题

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

题目链接🔗:https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/)

题目描述

给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]

你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。


解题思路

对于左端点我们二分找到第一个 >= target 的下标,记为 left,如果没有则为 -1

对于右端点我们二分找到最后一个 <= target 的下标,记为 right,如果没有则为 -1

最终的答案为 [left, right]

参考代码

  • Python

    class Solution:def searchRange(self, nums: List[int], target: int) -> List[int]:def bsearch_1(l, r): # 找到第一个 >= target的位置def check(index):if nums[index] >= target:return True # 说明当前 nums[mid] 太大了,答案下标应该在 <= index ,所以返回 True 缩小右端点return False# l 为左端点,r 为右端点,都是闭区间# 使用时只需修改check函数即可while l < r:mid = (l + r) // 2if check(mid):r = mid # check函数代表你需要进行的判断操作# 最终的答案会满足check条件else:l = mid + 1 # 一定是这么写 不用多想if l >= n or nums[l] != target: # 代表没有找到答案return -1return l # 此时的 l 为答案 (l == r)def bsearch_2(l, r): # 找到最后一个 <= target的位置def check(index):if nums[index] <= target: # 说明当前 nums[mid] 太小了,答案下标应该 >= index, 所以返回 True 缩小左端点return Truereturn False# l 为左端点,r 为右端点,都是闭区间# 使用时只需修改check函数即可while l < r:mid = (l + r + 1) // 2 # 注意这里要多加 1if check(mid):l = mid # check函数代表你需要进行的判断操作# 最终的答案会满足check条件else:r = mid - 1 # 一定是这么写 不用多想if l >= n or nums[l] != target: # 代表没有找到答案return -1return l # 此时的 l 为答案 (l == r)n = len(nums)left = bsearch_1(0, n - 1)# 找到左端点,即第一个 >= target 的位置right = bsearch_2(0, n - 1) # 找到右端点,即最后一个 <= target 的位置return [left, right]
    
  • Java

    class Solution {public int[] searchRange(int[] nums, int target) {int n = nums.length;int left = bsearch_1(nums, target, 0, n - 1);  // 找到左端点,即第一个 >= target 的位置int right = bsearch_2(nums, target, 0, n - 1); // 找到右端点,即最后一个 <= target 的位置return new int[]{left, right};}private int bsearch_1(int[] nums, int target, int l, int r) {while (l < r) {int mid = (l + r) / 2;if (nums[mid] >= target) {r = mid; // 说明当前 nums[mid] 太大了,答案下标应该在 <= index ,所以返回 True 缩小右端点} else {l = mid + 1; // 一定是这么写 不用多想}}if (l >= nums.length || nums[l] != target) {return -1; // 代表没有找到答案}return l; // 此时的 l 为答案 (l == r)}private int bsearch_2(int[] nums, int target, int l, int r) {
  • Cpp

    class Solution {
    public:vector<int> searchRange(vector<int>& nums, int target) {int n = nums.size();int left = bsearch_1(nums, target, 0, n - 1);  // 找到左端点,即第一个 >= target 的位置int right = bsearch_2(nums, target, 0, n - 1); // 找到右端点,即最后一个 <= target 的位置return {left, right};}private:int bsearch_1(vector<int>& nums, int target, int l, int r) {// l 为左端点,r 为右端点,都是闭区间while (l < r) {int mid = (l + r) / 2;if (check1(nums, mid, target)) {r = mid; // check函数代表你需要进行的判断操作// 最终的答案会满足check条件} else {l = mid + 1; // 一定是这么写 不用多想}}if (l >= nums.size() || nums[l] != target) {return -1; // 代表没有找到答案}return l; // 此时的 l 为答案 (l == r)}bool check1(vector<int>& nums, int index, int target) {if (nums[index] >= target) {return true; // 说明当前 nums[mid] 太大了,答案下标应该在 <= index ,所以返回 True 缩小右端点}return false;}int bsearch_2(vector<int>& nums, int target, int l, int r) {// l 为左端点,r 为右端点,都是闭区间while (l < r) {int mid = (l + r + 1) / 2; // 注意这里要多加 1if (check2(nums, mid, target)) {l = mid; // check函数代表你需要进行的判断操作// 最终的答案会满足check条件} else {r = mid - 1; // 一定是这么写 不用多想}}if (l >= nums.size() || nums[l] != target) {return -1; // 代表没有找到答案}return l; // 此时的 l 为答案 (l == r)}bool check2(vector<int>& nums, int index, int target) {if (nums[index] <= target) {return true; // 说明当前 nums[mid] 太小了,答案下标应该 >= index, 所以返回 True 缩小左端点}return false;}
    };

🍰 笔试真题

  • 直接考察二分查找位置的笔试题目并不多,更多是和其他算法相结合

  • 该题来自今年 阿里系春招 的笔试题,本题为最后一题,题目难度为中等偏上,其中涉及到了 质数筛 的数论知识,大家如果对这方面不熟悉可以先去了解一下,咱们后面开始数论篇的时候会详细讲解

  • 如果有困难的小伙伴这边可以根据参考代码 只写二分 的部分,本篇主目的是对二分查找进行介绍,可以等后续数论篇的时候再来补这里的 **质数筛 **部分~。

神奇数字

🔗评测链接:https://app5938.acapp.acwing.com.cn/contest/3/problem/Day02

题目描述

LYA 定义了一个神奇数字 n u m num num,其要满足 n u m = a 2 + b 3 + c 4 num = a^2 + b^3 + c^4 num=a2+b3+c4,其中 a , b , c a,b,c a,b,c 都为质数。于是 LYA 想知道在 1 ∼ n 1 \sim n 1n 中有多少个这样的神奇数字呢,请你告诉 LYA。

输入格式

第一行为 t t t,表示有 t t t 组数据。

接下来有 t t t 行,每行为一个整数 n n n

输出格式

输出为 t t t 行,每行为一组答案。

样例输入

3
28
33
47

样例输出

1
2
3

数据范围

  • 1 < t < 1 0 5 1 < t < 10^5 1<t<105
  • 1 ≤ n < 1 0 6 1 \leq n < 10^6 1n<106

题解

本题可以使用预处理 + 二分查找的方法来解决。

首先,预处理出所有可能的神奇数字。由于 a , b , c a,b,c a,b,c 都是质数,我们可以先用埃氏筛法筛选出 1 ∼ 1 0 6 1 \sim 10^6 1106 内的所有质数,存入数组 p r i m e prime prime 中。

然后,我们使用三重循环枚举所有可能的 a , b , c a,b,c a,b,c,计算出对应的神奇数字 v a l val val,并将其加入到集合 s s s 中。注意,为了避免重复计算,我们需要保证 a 2 + b 3 + c 4 < 1 0 6 a^2 + b^3 + c^4 < 10^6 a2+b3+c4<106,虽然有三重循环,但有大量剪枝,总计算次数在 3 × 1 0 5 3 \times 10 ^ 5 3×105 左右。

接下来,将集合 s s s 中的元素转移到数组 v v v 中,并对 v v v 进行排序。

最后,对于每个询问 n n n,我们使用二分查找在数组 v v v 中查找不超过 n n n 的元素个数,即为答案。

时间复杂度 O ( t log ⁡ n ) O(t \log n) O(tlogn),空间复杂度 O ( n ) O(n) O(n)

参考代码
  • Python
import sys
input = lambda: sys.stdin.readline().strip()
import bisectprime = []
N = 10 ** 6 + 1
st = [False] * Nfor i in range(2, N):if not st[i]:prime.append(i)for j in range(i, N, i):st[j] = True v = []
s = set()
n = len(prime)for a in prime:if a * a >= N:breakfor b in prime:if a * a + b ** 3 >= N:breakfor c in prime:val = a ** 2 + b ** 3 + c ** 4if val >= N:breaks.add(val)v = sorted(s)
# 将右边界跟新成一个比较大的数
v.append(10**9)
m = len(v)
t = int(input())
# ----- 以下为二分的部分 -----
for _ in range(t):x = int(input())# 找到 v 中不超过 x 的个数# 这里采用二分的第一种模版,找到 v 中第一个 > x 的下标# 也可以采用第二个模版,找到 v 中最后一个 <= x 的下标# 1.手写二分l, r = 0, m - 1while l < r:mid = (l + r) // 2if v[mid] > x:r = midelse:l = mid + 1print(l)# 2. 也可以用库函数实现# idx = bisect.bisect_right(v, x) # 库函数 返回 v 中第一个大于 x 的下标# print(idx)
  • Java
import java.util.*;public class Main {public static void main(String[] args) {int N = 1000001;boolean[] st = new boolean[N];List<Integer> prime = new ArrayList<>();for (int i = 2; i < N; i++) {if (!st[i]) prime.add(i);for (int j = i; j < N; j += i) st[j] = true;}Set<Long> s = new HashSet<>();int n = prime.size();for (int a : prime) {if ((long) a * a >= N) break;for (int b : prime) {if ((long) a * a + (long) b * b * b >= N) break;for (int c : prime) {long val = (long) a * a + (long) b * b * b + (long) c * c * c * c;if (val >= N) break;s.add(val);}}}List<Long> v = new ArrayList<>(s);Collections.sort(v);// 将右边界跟新成一个比较大的数,方便维护long maxv_right = 1000_000_000;v.add(maxv_right);Scanner scanner = new Scanner(System.in);int t = scanner.nextInt();int m = v.size();// ----- 以下为二分的部分 -----while (t-- > 0) {int x = scanner.nextInt();// 需要在 v 中找到 所有 <= x 的个数// 这里采用二分的第一种模版,找到 v 中第一个 > x 的下标// 也可以采用第二个模版,找到 v 中最后一个 <= x 的下标int L = 0, R = m;while(L < R){int mid = L + R >> 1;if(v.get(mid) > x) R = mid; elseL = mid + 1;}System.out.println(L);}}
}
  • Cpp
#include <bits/stdc++.h>using namespace std;
using ll = long long;const int N = 1e6 + 1;
bool st[N];
vector<int> prime;int main() {for (int i = 2; i < N; i++) {if (!st[i]) prime.push_back(i);for (int j = i; j < N; j += i) st[j] = true;}unordered_set<int> s;int n = prime.size();for (auto a : prime) {if (1ll * a * a >= N) break;for (auto b : prime) {if (1ll * a * a + b * b * b >= N) break;for (auto c : prime) {ll val = 1ll * a * a + b * b * b + c * c * c * c;if (val >= N) break;s.insert(val);}}}vector<int> v(s.begin(), s.end());sort(v.begin(), v.end());// 将右边界跟新成一个比较大的数,方便维护v.push_back(int(1e9));int m = v.size();int t;cin >> t;// ----- 以下为二分的部分 -----while (t--) {int x;cin >> x;// 需要在 v 中找到 所有 <= x 的个数// 这里采用二分的第一种模版,找到 v 中第一个 > x 的下标// 也可以采用第二个模版,找到 v 中最后一个 <= x 的下标// 1.手写二分int l = 0, r = m - 1;while(l < r){int mid = l + r >> 1;if(v[mid] > x) r = mid;elsel = mid + 1;}cout << l << "\n";// 2. 也可以用库函数实现// int idx = upper_bound(v.begin(), v.end(), x) - v.begin(); // STL实现,返回 v 中第一个大于 x 的下标// cout << idx << "\n";}return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/857709.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于CPWM与DPWM综合调制的光伏逆变器

1. 光伏并网逆变器矢量控制 图 1 为光伏发电系统常用的逆变器拓扑结 构,太阳能光伏电池板发电所产生的直流电能接 入光伏并网逆变器直流侧。逆变器将电能逆变, 经过滤波器与隔离升压变压器连接,最终并入电 网。其中隔离变压器低压侧漏感与LC滤波器组 成LCL滤波。为便于分析…

android | studio的UI布局和代码调试 | UI调试 (用于找到项目源码)

网上找到一个项目&#xff0c;想快速的搞懂是怎么实现的&#xff0c;搞了半天发现原来android都升级到Jetpack Compose了&#xff0c;然后去找源码挺不容易的&#xff0c;摸索中发现了这个调试的方法&#xff0c;还可以。 https://developer.android.com/studio/debug/layout-i…

谷歌Chrome浏览器排查js内存溢出

1. 打开谷歌浏览器检查台 2. 点击memory 3. 点击开始快照录制&#xff0c;时隔一会儿录一次&#xff0c;多录几次 4. 进行快照对比

【C++】STL中优先级队列的使用与模拟实现

前言&#xff1a;在前面我们学习了栈和队列的使用与模拟实现&#xff0c;今天我们来进一步的学习优先级队列使用与模拟实现 &#x1f496; 博主CSDN主页:卫卫卫的个人主页 &#x1f49e; &#x1f449; 专栏分类:高质量&#xff23;学习 &#x1f448; &#x1f4af;代码仓库:卫…

常见的8种排序(含代码):插入排序、冒泡排序、希尔排序、快速排序、简单选择排序、归并排序、堆排序、基数排序

时间复杂度O(n^2) 1、插入排序 (Insertion Sort) 从第一个元素开始&#xff0c;该元素可以认为已经被排序&#xff1b;取出下一个元素&#xff0c;在已经排序的元素序列中从后向前扫描&#xff1b;如果该元素&#xff08;已排序&#xff09;大于新元素&#xff0c;将该元素移到…

IPv6 address status lifetime

IPv6 地址状态转换 Address lifetime (地址生存期) 每个配置的 IPv6 单播地址都有一个生存期设置&#xff0c;该设置确定该地址在必须刷新或替换之前可以使用多长时间。某些地址设置为“永久”并且不会过期。“首选”和“有效”生存期用于指定其使用期限和可用性。 自动配置的…

vue中的状态管理

第1部分&#xff1a;引言 状态管理是应用中数据流动和变更的核心机制。在Vue应用中&#xff0c;状态管理不仅涉及到组件间的数据共享&#xff0c;还包括了数据的持久化、异步操作的处理等复杂场景。良好的状态管理策略可以提高应用的响应速度&#xff0c;降低组件间的耦合度&a…

分页查询前端对接

文章目录 添加角色修改角色当点击修改按钮后,那么就会弹出对话框,所以要设置显示为true点击修改的时候就是 要显示对话框 制作用户管理页面开发后端接口用户查询前端整合新增接口功能实现修改 添加角色 首先添加 添加表单的组件 那么总结一下 就是使用 组件 然后再使用变量接…

Python基础入门

目录 1. 什么是Python&#xff1f; 2. 安装Python 3. Python基础语法 4. 数据结构 5. 文件操作 6. Python标准库 总结 1. 什么是Python&#xff1f; Python是一种高级编程语言&#xff0c;由Guido van Rossum于1991年发布。它以其简单易读的语法和强大的功能而闻名&…

Nominatim免费的地址解析,逆地址解析,OpenStreetMap开源地图数据【全网最全】

视频学习地址 国内的一些地址解析供应商的API都开始付费了&#xff0c;就想找个免费的地址解析和逆地址解析的应用&#xff0c;最终选择了Nominatim OpenStreetMap 文章目录 一、选型1-1、数据源1-2、地理编码引擎2-1、初尝Nominatim2-1-1、地址解析2-1-2、逆地址解析 2-2、OS…

国内外大模型生态发展报告!

很多同学只知类似Check GPT或者说对国内的一些比较了解&#xff0c;对国外的不太了解&#xff0c;所以在这总结。 1 大模型的发展 左表 名称参数特点发布时间GPT-215亿英文底模&#xff0c;开源2019年Google T5110亿多任务微调, 开源2019年GPT-3.51750亿人工反馈微调2022年M…

UFS Power Mode Change 介绍

一. UFS Power Mode Change简介 1.UFS Power Mode指的是Unipro层的Power State, 也可以称为链路(Link)上的Power Mode, 可以通过配置Unipro Attribute, 然后控制切换Unipro Power State, 当前Power Mode Change有两种触发方式&#xff1a; (1) 通过DME Power Mode Change触发…

java中实现Callable方式创建线程

一、为啥要引入Callable 在前面讲了通过继承Thread和实现Runnable方式创建线程的区别&#xff0c;那为什么有了Runnable还要引入Callable?下面通过实现Runnable方式的弊端给出答案 实现Runnable方式的弊端&#xff1a; package java.lang; FunctionalInterface public inte…

1095 解码PAT准考证(测试点3)

solution 测试点3超时&#xff1a;命令为3时&#xff0c;用unordered_map而非map&#xff0c;否则会超时 #include<iostream> #include<string> #include<algorithm> #include<unordered_map> using namespace std; const int maxn 1e4 10; struct…

2024山东大学软件学院创新项目实训(9)使用OpenCompass进行模型评估

下载好OpenCompassData-core-20231110.zip 之后&#xff0c;解压压缩包 unzip OpenCompassData-core-20231110.zip 运行代码&#xff1a; python run.py --datasets ceval_gen --hf-path /hy-tmp/7B21/merged --tokenizer-path /hy-tmp/7B21/merged --tokenizer-kwargs p…

步步精:连接器领域的卓越品牌

自1987年成立以来&#xff0c;步步精坐落于美丽的旅游城市——温州市乐清虹桥镇&#xff0c;被誉为“国家电子主体生产基地”、“国家精密模具制造基地”。公司拥有7大厂区、9大事业部&#xff0c;800名专职员工&#xff0c;致力于提供高品质的连接器解决方案。注册商标“BBJCO…

百度ai人脸识别项目C#

一、项目描述 本项目通过集成百度AI人脸识别API&#xff0c;实现了人脸检测和识别功能。用户可以上传图片&#xff0c;系统将自动识别人脸并返回识别结果。 二、开发环境 Visual Studio 2019或更高版本.NET Framework 4.7.2或更高版本AForge.NET库百度AI平台人脸识别API 三、…

从网络配置文件中提取PEAP凭据

我的一位同事最近遇到了这样一种情况&#xff1a;他可以物理访问使用802.1X连接到有线网络的Windows计算机&#xff0c;同时保存了用于身份验证的用户凭据&#xff0c;随后他想提取这些凭据&#xff0c;您可能认为这没什么特别的&#xff0c;但是事情却有点崎岖波折…… 如何开…

攻防世界-5-1

下载文件发现是一个没有尾缀的文件&#xff0c;扔winhex&#xff0c;emmmm还是没看出来 搜了一圈&#xff0c;发现用xortool 得到key之后&#xff0c;跑一下脚本 得到flag&#xff1a; wdflag{You Are Very Smart}

pytest测试框架pytest-sugar插件生成进度条

Pytest提供了丰富的插件来扩展其功能&#xff0c;介绍下插件pytest-sugar&#xff0c;可以帮助我们在控制台中显示彩色的测试结果和进度条&#xff0c;提供失败的堆栈回溯信息。 为了使用 pytest-sugar&#xff0c;需要满足以下条件&#xff1a; Python 3.8 或更高版本pytest…