二分查找

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-l) >> 1;
            if (check(mid)) r = mid;
            else l = mid + 1;
        }
        
        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;
        }
        
        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 ∈ [1,(a+b)/2] 

  •  因为“每个人只能得到一种糖果” => ( 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;
    }
}

2023082601美团

给定一个整数z,now从0开始,你有两种操作:

1.no+x

2.no+y

限定条件:

每天可以操作1次操作1,操作2使用完后必须过至少2天才能再次使用。

问最少天数使得now  ≥  z 。

输入描述
第一行三个整数 x , y , z <= 109
输出描述
一个整数,输出最少次数。

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();
    }
}

  

 

2023041201华为

  •   问题背景:
    •   N 个客户端通过发送请求(表示为 R=[R1, R2, ..., RN])与核心购物系统进行通信。
    •   核心购物系统有最大调用量限制 cnt。
  • 降级规则:

    •   如果所有客户端请求总和 sum(R1, R2, ..., RN) 小于 cnt,则返回 -1。
    •   如果请求总和超过 cnt,需设定一个阈值 value。
    •   对于超过 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>13,将  value 设置为 2 ,则 1+2+2+2+2+1+2=12<13 。所以  value 为 22 。

in2:

1 7 8 8 1 0 2 4 9
7

out2:

0


  

样例解释

因为即使  value 设置为 1 , 1+1+1+1+1+1+1+1=8>7 也不满足,所以 value 只能为 0 

codes:

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;
    }
}

  

 2023032302QQ音乐

描述

有一个算法可以将外部声音转录为只包含小写字母的字符串 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. 两球之间的磁力

    1. 理解问题: 给定球的位置数组 position 和篮子数量 m,要求将这些球放入篮子中,使得任意两球间的最小磁力尽可能大。最小磁力指任意两球之间的距离,要使这个最小磁力最大化。

    2. 问题转化: 我们需要找到一个最大的最小磁力 x,使得能够将 m 个球放入篮子中,并且满足任意两球之间的距离至少为 x。我们可以通过二分搜索来确定这个 x 的值。

    3. 二分搜索算法:

      • 首先对 position 数组进行排序,以便更方便地计算任意两球之间的距离。
      • 设定二分搜索的范围。最小值 left 可以设为 1,最大值 right 则可以设为 position[position.length - 1] - position[0],即位置数组的最大距离。
      • 在二分搜索的过程中,对于每个中间值 mid,判断是否可以将 m 个球放入篮子中,使得任意两球之间的距离至少为 mid
        • 使用贪心的方法来模拟放球的过程:从第一个球开始,依次尝试将球放入位置,并确保每次放球的位置与上一个球的位置之间的距离不小于 mid
        • 如果可以放下所有球,说明当前 mid 值满足条件,将 left 向右移动;否则,将 right 向左移动。
    4. 具体步骤:

      • 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];//排序后第一个位置一定要放球(贪心)
        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 的「神奇数字」个数个数 , 那么 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 int minDays(int[] bloomDay, int m, int k) {
        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++;
            }
     
            if (cnt == k) {
                res++;
                i = j - 1;
            }else {
                i = j;
            }
        }
        return res >= m;
    }
}

思路:二分答案+动态规划

题目要求返回小偷至少需要具备的窃取能力(窃取能力是指小偷在不触发警报的情况下能够偷取的最大金额,但不能偷相邻的房屋,且至少要偷取 `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;
    }

2023040804美团

题目描述:

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;

    // 田地的状态,0表示空地,1表示有土豆
    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};
    // 队列,存储BFS的节点
    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);
        }

        // BFS队列的头尾指针
        int hh = 0, tt = -1;
        // 读取每个土豆的位置,并初始化它们的距离为0
        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初始化每个格子到最近土豆的距离
        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();

        // 二分搜索最大可能距离
        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);
    }

    // 检查是否存在路径使得路径上所有点到最近的土豆距离都不小于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];
                // 检查新位置是否在田地范围内,距离是否大于等于mid,且未访问过
                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 个回文子串的字符串。字符串的长度不得超过105

input:
4 // 一个正整数x,1≤x≤10e9
output:
rre

 

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. 无线网络

题解:二分答案+贪心构造

题目问 :请你输出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));
        }
    }
}

  

 

 

posted @ 2024-04-29 23:20  菠萝包与冰美式  阅读(5)  评论(0编辑  收藏  举报