二分查找
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; } }
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-l) >> 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); } }
将两种不同口味的糖果(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; } }
有一个算法可以将外部声音转录为只包含小写字母的字符串 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; } }
思路:二分+lcm(gcd)
设 f(x)表示为小于等于 x &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;span class="katex"&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;span class="katex-mathml"&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;span class="katex-html"&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;span class="base"&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;span class="strut"&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;span class="mord mathnormal"&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;的「神奇数字」个数个数 , 那么 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 递增而递增... 考虑二分答案
使用二分查找确定制作 m 束花所需的最少天数。
首先确定天数范围为最早和最晚的开花时间,然后在这个范围内进行二分查找。
对于每个中间值,检查是否能在这个天数内制作出至少 m 束花,根据制作的花束数量调整二分查找的范围,直到找到最小的满足条件的天数。
package com.coedes.binary_search.likou1482; import java.util.Arrays; /** * @description:https://leetcode.cn/problems/minimum-number-of-days-to-make-m-bouquets/ * @author: wenLiu * @create: 2024/4/29 22:01 */ public class Solution { public static void main(String[] args) { //bloomDay = [1,10,2,9,3,8,4,7,5,6], m = 4, k = 2 int[] bloomDay = {1, 10, 2, 9, 3, 8, 4, 7, 5, 6}; int m = 4, k = 2; System.out.println(new Solution().minDays(bloomDay, m, k)); } public int minDays(int[] bloomDay, int m, int k) { //题目问:请你返回从花园中摘 m 束花需要等待的最少的天数。 那就设 摘 m 束花需要等待的最少的天数为x填... // m(x) 表示 摘 m 束花与等待天数x函数 , 显然m(x) 随着 x 递增而递增... 考虑二分答案... int n = bloomDay.length; if ((long) m * k > n) return -1; // x ∈ [1, max(bloomDay)] int l = 1, r = Arrays.stream(bloomDay).max().getAsInt(); while (l < r) { int mid = l + (r - l) / 2; if (check(mid, m, k, bloomDay)) { r = mid; } else { l = mid + 1; } } return r; } private boolean check(int mid, int m, int k, int[] bloomDay) { int n = bloomDay.length; int res = 0; for (int i = 0; i < n - k + 1; i++) { int j = i, cnt = 0; while (j < n && bloomDay[j] <= mid && cnt < k) { cnt++; j++; } /* 这样写会导致死循环 bloomDay = [1,10,2,9,3,8,4,7,5,6] j = 1 bloomDay[1] > mid false => i = j-1 = 0 => for i++ => j =1 bloomDay[1] > mid false => .... 死循环 if (cnt == k) { res++; } i = j - 1;*/ if (cnt == k) { res++; i = j - 1; }else { i = j;//此时 bloomDay[j] > mid i=j++; } } return res >= m; } }
1. 问题描述:
一排房屋中每间房屋都有一定金额的现金。小偷想要偷取这些现金,但相邻的房屋有联通的防盗系统,不能同时被偷。
2. 输入:
- 整数数组 `nums` 表示每间房屋内的现金金额。
- 整数 `k` 表示小偷至少要偷取的房屋数量。
3. 输出:
返回小偷至少需要具备的窃取能力,使得他能够偷取至少 `k` 间房屋的最大金额。
思路:二分答案+动态规划
题目要求返回小偷至少需要具备的窃取能力(窃取能力是指小偷在不触发警报的情况下能够偷取的最大金额,但不能偷相邻的房屋,且至少要偷取 `k` 间房屋。),
题目字眼有最小值问题,先证明二分单调性,看看能不能用二分搜索。
老规矩,求什么设什么...设至少需要具备的窃取能力minCapacity为mid ,mid ∈ [min(nums),max(nums)]。
- 要使用二分搜索解决这个问题,需要确保问题具有二分的单调性,即随着窃取能力 `mid` 的增加,偷取至少 `k` 间房屋的能力具有单调性。
- 设 `check(mid)` 是一个用于判断以 `mid` 为窃取能力时,小偷是否能够偷取至少 `k` 间房屋的金额的函数:
- - 如果 `check(mid) = true`,表示以 `mid` 为窃取能力时,小偷可以偷取至少 `k` 间房屋的金额。
- - 如果 `check(mid) = false`,表示以 `mid` 为窃取能力时,小偷不能偷取至少 `k` 间房屋的金额。
- 根据上述定义,我们需要证明 `check(mid)` 具有单调性:
- - 如果 `mid1 < mid2`,并且 `check(mid1) = true`,那么 `check(mid2)` 也会是 `true`。因为窃取能力增加,偷取的房屋数量不会减少。
- - 同理,如果 `mid1 > mid2`,并且 `check(mid1) = false`,那么 `check(mid2)` 也会是 `false`。因为窃取能力减小,偷取的房屋数量不会增加。
- 因此,根据 `check(mid)` 的单调性,可以利用二分搜索在 `x` 上进行搜索,以确定小偷的最小窃取能力,使得小偷能够偷取至少 `k` 间房屋的最大金额而不触发警报。
对于chek函数,这个问题可以使用动态规划来解决,目标是计算小偷在不触发警报的情况下能够偷取的最大金额,但不能偷取相邻的房屋。其状态方程:
int n = nums.length;
int[] dp = new int[n + 1];//偷n个房间所能偷取房屋的最大值
d[0] = 0;//偷0个房间所能偷取房屋的最大值自然为0
dp[1] = nums[0] <= mid ? 1 : 0;//偷1个房间所能偷取房屋最大值
dp[i + 1] = Math.max(dp[i], dp[i - 1] + (nums[i] <= mid ? 1 : 0));
public int minCapability(int[] nums, int k) { int l = Arrays.stream(nums).min().getAsInt(); int r = Arrays.stream(nums).max().getAsInt(); while (l < r) { int mid = l + (r - l) / 2; if (check(mid, nums, k)) { r = mid; } else { l = mid + 1; } } return r; } private boolean check(int mid, int[] nums, int k) { int n = nums.length; int[] dp = new int[n + 1]; dp[1] = nums[0] <= mid ? 1 : 0; for (int i = 1; i < n; i++) { dp[i + 1] = Math.max(dp[i], dp[i - 1] + (nums[i] <= mid ? 1 : 0)); } return dp[n] >= k; }
2023美团-田地行走
题目描述:
1. 有一个 n * m 的田地,其中有 k 个格子埋有土豆。
2. 每个格子中埋有土豆的位置是已知的。
3. 我们想知道,在田地中任意两点之间移动时,移动路径上与土豆的最近距离的最大值是多少。
4. 移动只能沿上、下、左、右四个方向进行,不能移出田地范围。
5. 如果无论如何移动都会进入一个有土豆的格子,则返回最大可能值为 0。
输入描述:
- 第一行包含三个整数 n、m、k,分别表示田地的行数、列数和土豆个数。
- 接下来 k 行,每行包含两个整数 p、q,表示一个土豆放置在格子 (p, q) 中。
- 最后一行包含四个整数 x1、y1、x2、y2,表示塔子哥的出发位置和目的位置。保证出发位置和目的位置上没有土豆。
输出描述:
输出一行一个整数,表示移动过程中与土豆之间距离的最小值的可能最大值。
input: 5 6 2 2 1 2 3 1 1 5 1 output: 1
思路:
1. 使用多源 BFS 初始化每个格子到最近土豆的距离。
2. 问题转化为通过二分搜索找到最大的满足条件的距离 mid。
3. 使用 BFS 检查是否存在从起点到终点的路径,路径上所有点到最近的土豆距离都小于 mid。
package com.coedes.binary_search.meituan20230408;
/**
* @description:https://codefun2000.com/p/P1169
* @author: wenLiu
* @create: 2024/4/30 15:22
*/
import java.util.*;
public class Main {
private static final int N = 510;
private static final int INF = Integer.MAX_VALUE;
private static int[][] g = new int[N][N];
private static int[][] dist = new int[N][N];
private static boolean[][] vis = new boolean[N][N];
private static int n, m, k;
private static int sx, sy, tx, ty;
private static int[] dx = {-1, 0, 1, 0};
private static int[] dy = {0, 1, 0, -1};
private static int[][] q = new int[N * N][2];
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
m = sc.nextInt();
k = sc.nextInt();
for (int i = 1; i <= n; ++i) {
Arrays.fill(dist[i], INF);
}
int hh = 0, tt = -1;
for (int a = 1; a <= k; ++a) {
int x = sc.nextInt();
int y = sc.nextInt();
dist[x][y] = 0;
// 将所有土豆入队
q[++tt] = new int[]{x, y};
}
// BFS初始化dist
while (hh <= tt) {
int x = q[hh][0], y = q[hh][1];
hh += 1;
for (int i = 0; i < 4; ++i) {
int nx = x + dx[i], ny = y + dy[i];
if (nx >= 1 && nx <= n && ny >= 1 && ny <= m && dist[nx][ny] > dist[x][y] + 1) {
dist[nx][ny] = dist[x][y] + 1;
q[++tt] = new int[]{nx, ny};
}
}
}
sx = sc.nextInt();
sy = sc.nextInt();
tx = sc.nextInt();
ty = sc.nextInt();
// 二分答案,最大为 min(dist[sx][sy], dist[tx][ty])
int l = 0, r = Math.min(dist[sx][sy], dist[tx][ty]);
while (l < r) {
int mid = (l + r + 1) >> 1;
if (check(mid)) l = mid;
else r = mid - 1;
}
System.out.println(l);
}
// check 即按照 mid 这个最近距离,任何到任意一个点的距离小于 mid 的点都不可到达
private static boolean check(int mid) {
int hh = 0, tt = -1;
q[++tt] = new int[]{sx, sy};
vis[sx][sy] = true;
boolean is_find = false;
while (hh <= tt) {
int x = q[hh][0], y = q[hh][1];
hh += 1;
// 上下左右四个方向遍历
for (int i = 0; i < 4; ++i) {
int nx = x + dx[i], ny = y + dy[i];
if (nx >= 1 && nx <= n && ny >= 1 && ny <= m && dist[nx][ny] >= mid && !vis[nx][ny]) {
vis[nx][ny] = true;
q[++tt] = new int[]{nx, ny};
if (nx == tx && ny == ty) {
is_find = true;
break;
}
}
}
// 如果找到一条路径,提前退出,避免后续多余的搜索
if (is_find) break;
}
boolean ans = vis[tx][ty];
// 将到达过的点全部标记为空,方便下一次遍历,无需清空整个数组
for (int i = 0; i <= tt; ++i) vis[q[i][0]][q[i][1]] = false;
return ans;
}
}
题目描述:
找到由字符 'r', 'e', 和 'd' 构成的字符串中恰好有 x 个回文子串的字符串。字符串的长度不得超过10e5。
input: 4 // 一个正整数x,1≤x≤10e9 output: rre
思路:二分答案+贪心构造
1.连续的一段相同字母,回文子串个数最大。 1+2+3+...+n = (n+1)*n/2
2.按照r,d,e,r,d,e,...这样的顺序来放置连续的段,那么每个段之间就是独立的,即不会产生跨段的回文子串.
3.由于字符串长度有限制,x 又比较大,所以我们可以采取贪心的策略,每次放尽量长的相同段(这个过程可以二分),使得最终构造出来的段尽量小
二分遍历字符串长度n(1<=n<=10e5),mid 表示字符串长度为n的最大回文串子串数量,贪心思想mid越大越好(说明连续的单个字符越多,形成的回文子串数量越大),所以mid =(l+r+1)/2;
找到第一个小于x的mid值返回,依次贪心构造长度为mid的连续单个字母字符串(rrrr... len = mid)
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; /** * @description:https://codefun2000.com/p/P1080 * @author: wenLiu * @create: 2024/5/5 22:02 */ public class Main { public static void main(String[] args) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); int x = Integer.parseInt(reader.readLine()); char[] colors = new char[]{'r', 'e', 'd'}; int index = 0; StringBuffer res = new StringBuffer(); //贪心构造 while (x > 0) { //二分答案,找到回文子串最大数量小于等于x的字符串长度n int n = check(x); //依次构造长度为n的连续段 for (int i = 0; i < n; i++) { res.append(colors[index]); } index = (index + 1) % 3; x -= (((n + 1) * n) / 2); } System.out.println(res.toString()); } private static int check(int x) { int l = 1, r = (int) 10e5; while (l < r) { int mid = (l + r + 1) / 2; // (n+1)n/2 //找到第一个 小于 x if (1L * (mid + 1) * mid / 2 >= x) { r = mid - 1; } else { l = mid; } } return l; } }
5048. 无线网络
题解:二分答案+贪心构造
题目问 :请你输出 &amp;amp;amp;lt;span id="MathJax-Span-37" class="mrow"&amp;amp;amp;gt;&amp;amp;amp;lt;span id="MathJax-Span-38" class="mi"&amp;amp;amp;gt;r 的最小可能值...
=> r 和 安装基站是否合理显然符合二分单调性 => 因此可以对r进行二分查找 => l = 0 , r = 2*10e9
注意
基站的安装位置坐标不一定是整数。由于r的范围可能会有小数(0.5),因此把所有坐标的数都乘上2,再二分出答案
奶牛位置对基站没有影响可以先对奶牛位置排序。
题目问:合理的基站安装位置方案。
通过二分答案得到的mid , 第一个基站安装位置为 x[0]+mid , 第二个基站安装位置为 x[0]+2*mid+1,第三个同理,可以用循环模拟该过程+记数,最后判断遍历完所有奶牛位置需要安装基站数是否小于等于3且最后一个基站覆盖范围要包含最后一个奶牛.
import java.util.*; public class Main { public static boolean check(int[] a, int n, int x) { int cnt = 1; long last = a[0] + x; for (int i = 0; i < n; i++) { if (last + x >= a[i]) { continue; } last = a[i] + x; cnt++; } return cnt <= 3 && last + x >= a[n - 1]; } public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int n = scanner.nextInt(); int[] a = new int[n]; for (int i = 0; i < n; i++) { int x = scanner.nextInt(); a[i] = 2 * x; } Arrays.sort(a); int l = 0, r = 2000000000; while (l < r) { int mid = l + (r - l) / 2; if (check(a, n, mid)) { r = mid; } else { l = mid + 1; } } System.out.printf("%.6f\n", r / 2.0); List<Double> res = new ArrayList<>(); double last = a[0] + r; res.add(last / 2.0); for (int i = 0; i < n; i++) { if (a[i] <= last + r) { continue; } last = a[i] + r; res.add(last / 2.0); } while (res.size() < 3) { res.add(0.0); } for (int i = 0; i < 3; i++) { System.out.printf("%.6f ", res.get(i)); } } }