1.0 二分查找概念
key words: 单调区间、最大化最小值(最小化最大值)、时间复杂度O(logn)
1.1 二分模板
模板来自于 AK机大厂笔试 星球。
1.1.1 在非递减数组中找到第一个 ≥ x的数
public int lowerBound(int[] nums, int x) { int l = 0, r = nums.length - 1; while (l < r) { int mid = l+(r-l)>>1; if (nums[mid] >= x) { r = mid; } else { l = mid + 1; } } if (nums[l] < x) { return -1; } else { return l; } }
1.1.2 在非递减数组中找到第一个>x的数
public int upperBound(int[] nums, int x) { int l = 0, r = nums.length - 1; while (l < r) { int mid = l+(r-l)>>1; if (nums[mid] > x) { r = mid; } else { l = mid + 1; } } if (nums[l] > x) { return l; } else { return -1; } }
LeetCode 34. 在排序数组中查找元素的第一个和最后一个位置
找到第一个等于target下标l
找到第一个大于target下标l1
返回 (l,l1)
package com.coedes.binary_search.likou34; /** * @description:https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/description/?envType=list&envId=yhAjDBEG * @author: wenLiu * @create: 2024/4/22 14:00 */ public class Solution { public int[] searchRange(int[] nums, int target) { int n = nums.length; if (n == 0) return new int[]{-1, -1}; int l = 0, r = n - 1; //找到第一个target值 (binarysearch >= target) while (l < r) { int mid = (l + r) / 2; if (nums[mid] >= target) { r = mid; } else { l = mid + 1; } } if (nums[l] != target) { return new int[]{-1, -1}; } else { //找到第一个大于target 下标 int l1 = 0, r1 = n - 1; while (l1 < r1) { int mid = (l1 + r1) / 2; if (nums[mid] > target) { r1 = mid; } else { l1 = mid + 1; } } if (nums[l1]==target) { return new int[]{l, l1}; }else { return new int[]{l, l1 - 1}; } } } }
1.2 二分答案
1.2.1 二分答案模板
① 最大值最小化(还有叫最小化最大值的...注意理解就是求最大值里面最小的那个....题目求的是一个最小值)
import java.util.*; public class Main { // 判断当前枚举的答案x是否合法 static boolean check(int x) { // 在这里实现具体的判断逻辑 return true; } public static void main(String[] args) { int l = 0, r = (int)1e9; while (l < r) { int mid = (l + r) >> 1; if (check(mid)) r = mid; else l = mid + 1; } // 最后的答案是l System.out.println(l); } }
②最小值最大化(求的是一个最大值)
import java.util.*; public class Main { // 判断当前枚举的答案x是否合法 static boolean check(int x) { // 在这里实现具体的判断逻辑 return true; } public static void main(String[] args) { int l = 0, r = (int)1e9; while (l < r) { int mid = (l + r + 1) >> 1; if (check(mid)) l = mid; else r = mid - 1; } // 最后的答案是l System.out.println(l); } }
2023美团-分糖果
将两种不同口味的糖果(a 个和 b 个)分给 n 个人。
确保每块糖果都被分配。
每个人只能得到一种糖果。
考虑如何分配这些糖果以使得分得最少糖果的人获得尽可能多的糖果。
输入描述
第一行一个正整数 T ,表示有 T 组数据。
对于每一组数据,输入一行n,a,b,中间用空格隔开。
1<a,b<10000,2≤n≤a+b,1≤T≤100
输出描述
对于每一组数据,输出仅一行一个整数,表示答案。
in: 2 5 2 3 4 7 10 out: 1 3
题解:二分答案+
check
- “考虑如何分配这些糖果以使得分得最少糖果的人获得尽可能多的糖果。” => 这是一道最小化最大值题目 => mid = (l+r+1)>>1
- 题目问“如何分配这些糖果” => 设 每个人分 x 个 。
=> x 定义域? => n ≤ a+b => 当 n = a+b => x ≥ 1
=> x ≤(a+b)/ n
=> x ∈ [1,(a+b)/n]
- 因为“每个人只能得到一种糖果” => ( a/x + b/x ) >= n
- 单调性证明:f(x) = a/x + b/x , 分给单孩子糖果越多 x,则分到糖果孩子数量就越少 f(x) ,f(x)是个单调递减函数,可以使用二分。
- 总的来说,问题最后转化为: x ∈ [1,(a+b)/n] ,枚举x找到满足( a/x + b/x ) >= n条件下x的最值。
package com.coedes.binary_search.meituan20230415; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; /** * @description:https://xuq7bkgch1.feishu.cn/docx/CAbedNJ5KobvinxdyKgcKsrlnrd * @author: wenLiu * @create: 2024/4/22 15:22 */ public class Main { public static void main(String[] args) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); int T = Integer.parseInt(reader.readLine()); for (int i = 0; i < T; i++) { String[] s = reader.readLine().split(" "); int n = Integer.parseInt(s[0]); int a = Integer.parseInt(s[1]); int b = Integer.parseInt(s[2]); System.out.println(helper(n, a, b)); } } private static int helper(int n, int a, int b) { int l = 1, r = (a + b) / 2; while (l < r) { int mid = (l + r + 1) >> 1; if (check(a, b, mid, n)) { l = mid; } else { r = mid - 1; } } return l ; } private static boolean check(int a, int b, int x, int n) { return (a / x + b / x) >= n; } }
2023.08.26-美团-第一题
给定一个整数z,now从0开始,你有两种操作:
1.now += x
2.now += y
限定条件:
每天可以操作1次操作1,操作2使用完后必须过至少2天才能再次使用。
问最少天数使得now ≥ z 。
输入描述
第一行三个整数 x , y , z <= 1e9
输出描述
一个整数,输出最少次数。
in:1 2 10 out:6
模拟:
package com.coedes.binary_search.meituan20230826; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; /** * @description:https://codefun2000.com/p/P1493 * @author: wenLiu * @create: 2024/4/22 19:10 */ public class Main { public static void main(String[] args) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); String[] s = reader.readLine().split(" "); long x = Long.parseLong(s[0]); long y = Long.parseLong(s[1]); long z = Long.parseLong(s[2]); int cnt = 2; long now = 0; long res = 0; while (now < z) { if (cnt == 2) { now += (x + y); cnt = 0; } else { cnt++; now += x; } res++; } System.out.println(res); } }
可以发现 now 是以 3 * x + y为一组进行变化...
import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int x, y, z; // 输入 x、y、z 的值 x = scanner.nextInt(); y = scanner.nextInt(); z = scanner.nextInt(); // 计算余数 int rest = z % (3 * x + y); // 根据不同的余数情况进行计算和输出 if (rest == 0) { System.out.println((z / (3 * x + y)) * 3); } else { if (rest <= x + y) {// 一天能够完成 System.out.println((z / (3 * x + y)) * 3 + 1); } else if (rest <= 2 * x + y) {// 两天能够完成 System.out.println((z / (3 * x + y)) * 3 + 2); } else {// 三天能够完成 System.out.println((z / (3 * x + y)) * 3 + 3); } } scanner.close(); } }
2023.04.12-实习笔试-第一题-购物系统的降级策略
- 问题背景:
- N 个客户端通过发送请求(表示为 R=[R1, R2, ..., RN])与核心购物系统进行通信。
- 核心购物系统有最大调用量限制 cnt。
-
降级规则:
- 如果所有客户端请求总和 sum(R1, R2, ..., RN) 小于 cnt,则返回 -1。
- 如果请求总和超过 cnt,需设定一个阈值 valu。
- 对于超过 value 的单个客户端的请求量,必须限制为 value,而其他未达到 value 的客户端请求可以正常发起。
-
问题目标:
- 求出最大的 value(value 可以为0)。
- 保证客户端请求总量不超过核心购物系统的最大调用量 cnt。
- value 要尽可能大,以确保交易顺利进行。
输入描述
第一行:每个客户端的调用量(整型数组)
第二行:核心购物系统的最大调用量
0< R.length ≤ 105,0 ≤ R[i] ≤ 105 ,0 ≤ cnt ≤ 109
输出描述
调用量的阈值 value
in1: 1 4 2 5 5 1 6 13 out1: 2
样例解释
因为 1+4+2+5+5+1+6>131+4+2+5+5+1+6>13 ,将 value 设置为 22 ,则 1+2+2+2+2+1+2=12<131+2+2+2+2+1+2=12<13 。所以 value 为 22 。
in2:
1 7 8 8 1 0 2 4 9
7
out2:
0
样例解释
因为即使 value 设置为 11 , 1+1+1+1+1+1+1+1=8>71+1+1+1+1+1+1+1=8>7 也不满足,所以 value 只能为 0
思路:
根据题目“要求求出最大的 value(value 可以为0)” => 很明显自变量为 value ,其下界为0。 上界我开始认为是cnt,后面想了想,既然已知请求序列,那么最大值可以缩小为max(Ri) 。
所以 value ∈ [0,max(Ri)] , 设f(value)为进入核心购物模块请求数量,则 f(value) = sum(min(Ri,value))&& f(value) <= cnt; 显然value越小f(value)越大 ,函数单调,可以考虑二分结果。
题目为最小化求最大值,采用模板②,mid = (l+r+1)>>1 ,l = 0 , r = max(Ri)
package com.coedes.binary_search.huawei20230412; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.Arrays; import java.util.Map; /** * @description:https://codefun2000.com/p/P1189 * @author: wenLiu * @create: 2024/4/22 19:34 */ public class Main { public static void main(String[] args) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); String[] strOfR = reader.readLine().split(" "); int n = strOfR.length; int[] R = new int[n]; for (int i = 0; i < n; i++) { R[i] = Integer.parseInt(strOfR[i]); } long cnt = Long.parseLong(reader.readLine()); int maxRi = Arrays.stream(R).max().getAsInt(); int l = 0, r = maxRi;//O(logn) while (l < r) { int mid = (l + r + 1) >> 1; if (check(mid, cnt, R)) { l = mid; } else { r = mid - 1; } } if (r == maxRi) { System.out.println(-1); } else { System.out.println(r); } } private static boolean check(int mid, long cnt, int[] R) { long sum = 0; int n = R.length; for (int i = 0; i < n; i++) { sum += Math.min(mid, R[i]); if (sum > cnt) { return false; } } return true; } }
2023腾讯音乐-划分字符串
描述
有一个算法可以将外部声音转录为只包含小写字母的字符串 s,并计算该字符串的权值 w(s),定义为 s 的长度乘以 s 中不同字母的个数。
例如,对于字符串 s = "abacb",其权值为 w("abacb") = 5 * 3 = 15。
接着,算法将字符串 s 切分为 k 个连续的子串 s1, s2, …, sk,以使这 k 个子串中权值最大的子串的权值尽可能小(其中 1 <= k <= s.length<= 500000)。
你需要输出切分后权值最大的子串的权值。
input: adadasda 3 output: 8
思路:
“k 个子串中权值最大的子串的权值尽可能小” =>“输出切分后权值最大的子串的权值” => 小的里面找最大的=> 最大化最小值...
题目要求的是最大的子串权值,设其为x.
x ∈ [1,500000*26]
1. 使用二分查找确定最大子串权值 `x` 的最小可能取值。
2. 在二分查找中,通过调用 `check` 方法判断当前尝试的最大子串权值 `mid` 是否满足条件。
3. `check` 方法用于判断给定最大子串权值 `x` 是否能划分出不超过 `k` 个连续子串:
- 遍历字符串 `s`,维护当前子串的字符集合 `st` 和长度 `len`。
- 当子串权值超过 `x` 时,表示当前子串结束,增加子串计数。
4. 根据 `check` 方法的结果,调整二分查找的边界。
5. 最终输出满足条件的最小化最大值,即最大子串权值的最小可能取值。
这个算法通过简单描述了二分查找和子串划分的思路,用于解决最小化最大值问题。
package com.coedes.binary_search.txmusic20230323;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashSet;
import java.util.Set;
/**
* @description:https://xuq7bkgch1.feishu.cn/docx/CAbedNJ5KobvinxdyKgcKsrlnrd
* @author: wenLiu
* @create: 2024/4/25 18:04
*/
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
String s = reader.readLine();
int k = Integer.parseInt(reader.readLine());
int n = s.length();
int l = 1;
int r = 26*500000;
while (l < r) {
int mid = (l + r) / 2;
if (check(s, k, mid, n)) {
r = mid;
} else {
l = mid + 1;
}
}
System.out.println(l);
}
private static boolean check(String s, int k, int x, int n) {
int cnt = 0;
int len = 0;
Set<Character> st = new HashSet<>();
for (int i = 0; i < n; i++) {
st.add(s.charAt(i));
len++;
if (st.size() * len > x) {
cnt++;
st.clear();
len = 0;
i--;
}
}
if (!st.isEmpty()) {
cnt++;
}
return cnt <= k;
}
}
LeetCode 1552. 两球之间的磁力
-
理解问题: 给定球的位置数组
position
和篮子数量m
,要求将这些球放入篮子中,使得任意两球间的最小磁力尽可能大。最小磁力指任意两球之间的距离,要使这个最小磁力最大化。 -
问题转化: 我们需要找到一个最大的最小磁力
x
,使得能够将m
个球放入篮子中,并且满足任意两球之间的距离至少为x
。我们可以通过二分搜索来确定这个x
的值。 -
二分搜索算法:
- 首先对
position
数组进行排序,以便更方便地计算任意两球之间的距离。 - 设定二分搜索的范围。最小值
left
可以设为1
,最大值right
则可以设为position[position.length - 1] - position[0]
,即位置数组的最大距离。 - 在二分搜索的过程中,对于每个中间值
mid
,判断是否可以将m
个球放入篮子中,使得任意两球之间的距离至少为mid
。- 使用贪心的方法来模拟放球的过程:从第一个球开始,依次尝试将球放入位置,并确保每次放球的位置与上一个球的位置之间的距离不小于
mid
。 - 如果可以放下所有球,说明当前
mid
值满足条件,将left
向右移动;否则,将right
向左移动。
- 使用贪心的方法来模拟放球的过程:从第一个球开始,依次尝试将球放入位置,并确保每次放球的位置与上一个球的位置之间的距离不小于
- 首先对
-
具体步骤:
- 对
position
数组进行排序。 - 使用二分搜索确定最大的满足条件的
x
值。 - 在每个二分步骤中,使用贪心算法检查当前
mid
值是否可以满足将m
个球放入篮子中的条件
- 对
package com.coedes.binary_search.likou1552; import java.util.Arrays; /** * @description:https://leetcode.cn/problems/magnetic-force-between-two-balls/ * @author: wenLiu * @create: 2024/4/25 21:16 */ public class Solution { public int maxDistance(int[] position, int m) { //使得任意两球间 最小磁力 最大 =>最大化最小值 => 最小值最大化 => 求mid最大值 Arrays.sort(position); int l = 1; int r = position[position.length - 1] - position[0]; while (l < r) { int mid = (l + r + 1) >> 1; if (check(mid, m, position)) { l = mid; } else { r = mid - 1; } } return l; } private boolean check(int mid, int m, int[] position) { int cnt = 0; int pre = position[0];//排序后第一个位置一定要放球(贪心)假设有3个篮子2个球 如果第一个篮子不放球那么2球间距离就不是最大值了 cnt++; for (int i = 1; i < position.length; i++) { if (position[i] - pre >= mid) {//position排序后几不用abs(position[i]-pre) pre = position[i]; cnt++; } } return cnt >= m; } }
LeetCode 878. 第 N 个神奇数字
思路:二分+lcm(gcd)
设 f(x)表示为小于等于 x <span class="katex"><span class="katex-mathml"><span class="katex-html"><span class="base"><span class="strut"><span class="mord mathnormal">的「神奇数字」个数个数 , 那么 f(x)等于从1到x能被a整除个数+能被b整除的个数-既能被a整除又能被b整除个数(a,b最小公倍数)
- f(x)=⌊x/a⌋+⌊x/b⌋−⌊x/c⌋
- c = lcm(a,b) = a*b/gcd(a,b) = a/gcd(a,b)*b (防止int溢出先除后乘)
用较大的数除以较小的数,得到余数。
将较小的数和余数作为新的两个数,重复步骤1,
直到余数为0余数为0时的除数即为两数的最大公约数。
int gcd(int x,int y){
return y==0?x:gcd(y,x%y) ;
}
超时了...
package com.coedes.binary_search.likou878; /** * @description:https://leetcode.cn/problems/nth-magical-number/description/?envType=list&envId=yhAjDBEG * @author: wenLiu * @create: 2024/4/25 23:07 */ public class Solution { static final int mod = (int) (10E9 + 7); public int nthMagicalNumber(int n, int a, int b) { int l = 1, r = (int) 10E9; while (l < r) { int mid = (l + r) >> 1; if (check(a, b, mid) >= n) { r = mid; } else { l = mid + 1; } } return r % mod; } private int check(int a, int b, int mid) { return mid / a + mid / b - mid / lcm(a, b); } private int lcm(int a, int b) { return a * b / gcd(a, b); } private int gcd(int a, int b) { return b == 0 ? a : gcd(b, a % b); } }
参考大佬优化的上界r 灵茶山艾府 当 x= min(a,b)·n时,至少就有 n 个神奇数字了因此可以取它为二分上界,注意还要开long,因为n达到了 10e9...
LeetCode 1482. 制作 m 束花所需的最少天数
思路:二分答案+双指针
题目问:请你返回从花园中摘 m 束花需要等待的最少的天数。 那就设 摘 m 束花需要等待的最少的天数为 x 天。
m(x) 为 摘 m 束花与等待天数x函数 , 显然m(x) 随着 x 递增而递增... 考虑二分答案
最少的天数 => 找最小值 => mid = l + (r-1)>>1;
check函数用来判断
标签:二分,cnt,return,int,mid,查找,import,public From: https://www.cnblogs.com/alohablogs/p/18150433