3161.物块放置查询

目标

有一条无限长的数轴,原点在 0 处,沿着 x 轴 正 方向无限延伸。

给你一个二维数组 queries ,它包含两种操作:

  1. 操作类型 1 :queries[i] = [1, x] 。在距离原点 x 处建一个障碍物。数据保证当操作执行的时候,位置 x 处 没有 任何障碍物。
  2. 操作类型 2 :queries[i] = [2, x, sz] 。判断在数轴范围 [0, x] 内是否可以放置一个长度为 sz 的物块,这个物块需要 完全 放置在范围 [0, x] 内。如果物块与任何障碍物有重合,那么这个物块 不能 被放置,但物块可以与障碍物刚好接触。注意,你只是进行查询,并 不是 真的放置这个物块。每个查询都是相互独立的。

请你返回一个 boolean 数组results ,如果第 i 个操作类型 2 的操作你可以放置物块,那么 results[i] 为 true ,否则为 false 。

示例 1:

输入:queries = [[1,2],[2,3,3],[2,3,1],[2,2,2]]
输出:[false,true,true]
解释:
查询 0 ,在 x = 2 处放置一个障碍物。在 x = 3 之前任何大小不超过 2 的物块都可以被放置。

示例 2:

输入:queries = [[1,7],[2,7,6],[1,2],[2,7,5],[2,7,6]]
输出:[true,true,false]
解释:
查询 0 在 x = 7 处放置一个障碍物。在 x = 7 之前任何大小不超过 7 的物块都可以被放置。
查询 2 在 x = 2 处放置一个障碍物。现在,在 x = 7 之前任何大小不超过 5 的物块可以被放置,x = 2 之前任何大小不超过 2 的物块可以被放置。

说明:

  • 1 <= queries.length <= 15 * 10^4
  • 2 <= queries[i].length <= 3
  • 1 <= queries[i][0] <= 2
  • 1 <= x, sz <= min(5 10^4, 3 queries.length)
  • 输入保证操作 1 中,x 处不会有障碍物。
  • 输入保证至少有一个操作类型 2 。

思路

代码

性能

33.搜索旋转排序数组

目标

整数数组 nums 按升序排列,数组中的值 互不相同 。

在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。

给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。

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

示例 1:

输入:nums = [4,5,6,7,0,1,2], target = 0
输出:4

示例 2:

输入:nums = [4,5,6,7,0,1,2], target = 3
输出:-1

示例 3:

输入:nums = [1], target = 0
输出:-1

说明:

  • 1 <= nums.length <= 5000
  • -10^4 <= nums[i] <= 10^4
  • nums 中的每个值都 独一无二
  • 题目数据保证 nums 在预先未知的某个下标上进行了旋转
  • -10^4 <= target <= 10^4

思路

数组 nums元素值互不相同,它由一个升序数组经过旋转而成,返回 target 在数组中的下标,如果不在返回 -1,要求时间复杂度为 O(log n)

所谓的旋转可以理解为以 k 为分界点将数组分为两段,后面的放到前面,前面的放到后面。相当于两个有序数组拼在一起,且前面数组的每个元素都比后面数组的每个元素都大

  • 如果 target 在左侧,m 在右侧,直接排除右侧
  • 如果 target 在右侧,m 在左侧,直接排除左侧
  • 如果在同一侧直接按照正常的有序二分即可

进阶的版本是 81.搜索旋转排序数组II

代码


/**
 * @date 2026-05-22 09:13
 */
public class Search33 {

    public int search_v3(int[] nums, int target) {
        int r = nums.length - 1, l = 0;
        int m = l + (r - l) / 2;
        while (l <= r) {
            // 如果 target 在左侧,m 在右侧,直接排除右侧
            if (target >= nums[0] && nums[m] < nums[0]) {
                r = m - 1;
            } else if (target < nums[0] && nums[m] >= nums[0]) {
                // 如果 target 在右侧,m 在左侧,直接排除左侧
                l = m + 1;
                // 如果在同一侧直接按照正常的有序二分即可
            } else if (nums[m] == target) {
                return m;
            } else if (nums[m] < target) {
                l = m + 1;
            } else {
                r = m - 1;
            }
            m = l + (r - l) / 2;
        }
        return -1;
    }

}

性能

2540.最小公共值

目标

给你两个整数数组 nums1 和 nums2 ,它们已经按非降序排序,请你返回两个数组的 最小公共整数 。如果两个数组 nums1 和 nums2 没有公共整数,请你返回 -1 。

如果一个整数在两个数组中都 至少出现一次 ,那么这个整数是数组 nums1 和 nums2 公共 的。

示例 1:

输入:nums1 = [1,2,3], nums2 = [2,4]
输出:2
解释:两个数组的最小公共元素是 2 ,所以我们返回 2 。

示例 2:

输入:nums1 = [1,2,3,6], nums2 = [2,3,4,5]
输出:2
解释:两个数组中的公共元素是 2 和 3 ,2 是较小值,所以返回 2 。

说明:

  • 1 <= nums1.length, nums2.length <= 10^5
  • 1 <= nums1[i], nums2[j] <= 10^9
  • nums1 和 nums2 都是 非降序 的。

思路

有两个升序数组 nums1nums2,返回它们最小的公共元素。

双指针,如果不相等优先移动元素值较小的下标。

代码


/**
 * @date 2026-05-19 8:46
 */
public class GetCommon2540 {

    public int getCommon(int[] nums1, int[] nums2) {
        for (int a = 0, b = 0; a < nums1.length && b < nums2.length; ) {
            if (nums1[a] == nums2[b]) {
                return nums1[a];
            } else if (nums1[a] < nums2[b]) {
                a++;
            } else {
                b++;
            }
        }
        return -1;
    }
}

性能

154.寻找旋转排序数组中的最小值II

目标

已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后,得到输入数组。例如,原数组 nums = [0,1,4,4,5,6,7] 在变化后可能得到:

  • 若旋转 4 次,则可以得到 [4,5,6,7,0,1,4]
  • 若旋转 7 次,则可以得到 [0,1,4,4,5,6,7]

注意,数组 [a[0], a[1], a[2], ..., a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], ..., a[n-2]] 。

给你一个可能存在 重复 元素值的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。

你必须尽可能减少整个过程的操作步骤。

示例 1:

输入:nums = [1,3,5]
输出:1

示例 2:

输入:nums = [2,2,2,0,1]
输出:0

提示:

n == nums.length
1 <= n <= 5000
-5000 <= nums[i] <= 5000
nums 原来是一个升序排序的数组,并进行了 1 至 n 次旋转

进阶:这道题与 153.寻找旋转排序数组中的最小值 类似,但 nums 可能包含重复元素。允许重复会影响算法的时间复杂度吗?会如何影响,为什么?

思路

数组 nums 由一个升序数组经过旋转而成,求数组的最小值,要求时间复杂度为 O(log n)

153.寻找旋转排序数组中的最小值 不同的是可能存在重复元素。预处理 r,使它指向不等于 nums[0] 的最大下标即可。

代码


/**
 * @date 2026-05-18 11:13
 */
public class FindMin154 {

    public int findMin(int[] nums) {
        int n = nums.length;
        int l = 0, r = n - 1;
        while (r >= 0 && nums[0] == nums[r]) {
            r--;
        }
        int m = l + (r - l) / 2;
        while (l <= r) {
            if (nums[m] >= nums[0]) {
                l = m + 1;
            } else {
                r = m - 1;
            }
            m = l + (r - l) / 2;
        }
        return nums[l % n];
    }

}

性能

153.寻找旋转排序数组中的最小值

目标

已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后,得到输入数组。例如,原数组 nums = [0,1,2,4,5,6,7] 在变化后可能得到:

  • 若旋转 4 次,则可以得到 [4,5,6,7,0,1,2]
  • 若旋转 7 次,则可以得到 [0,1,2,4,5,6,7]

注意,数组 [a[0], a[1], a[2], ..., a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], ..., a[n-2]] 。

给你一个元素值 互不相同 的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。

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

示例 1:

输入:nums = [3,4,5,1,2]
输出:1
解释:原数组为 [1,2,3,4,5] ,旋转 3 次得到输入数组。

示例 2:

输入:nums = [4,5,6,7,0,1,2]
输出:0
解释:原数组为 [0,1,2,4,5,6,7] ,旋转 4 次得到输入数组。

示例 3:

输入:nums = [11,13,15,17]
输出:11
解释:原数组为 [11,13,15,17] ,旋转 4 次得到输入数组。

说明:

  • n == nums.length
  • 1 <= n <= 5000
  • -5000 <= nums[i] <= 5000
  • nums 中的所有整数 互不相同
  • nums 原来是一个升序排序的数组,并进行了 1 至 n 次旋转

思路

数组 nums元素值互不相同,它由一个升序数组经过旋转而成,求数组的最小值,要求时间复杂度为 O(log n)

所谓旋转,可以视为循环数组向右移动。虽然不是整体有序,但还是可以使用二分。由于元素值互不相同,根据第一个元素可以判断属于哪一部分,进而可以确定搜索方向。

如果大于第一个元素向右搜索,如果小于则向左搜索,注意如果返回的下标是 n 需要对 n 取余指向第一个元素。

代码


/**
 * @date 2024-08-31 22:37
 */
public class FindMin153 {

    public int findMin_v2(int[] nums) {
        int n = nums.length;
        int l = 0, r = n - 1;
        int m = l + (r - l) / 2;
        while (l <= r) {
            if (nums[m] >= nums[0]) {
                l = m + 1;
            } else {
                r = m - 1;
            }
            m = l + (r - l) / 2;
        }
        return nums[l % n];
    }

}

性能

1674.使数组互补的最少操作次数

目标

给你一个长度为 偶数 n 的整数数组 nums 和一个整数 limit 。每一次操作,你可以将 nums 中的任何整数替换为 1 到 limit 之间的另一个整数。

如果对于所有下标 i(下标从 0 开始),nums[i] + nums[n - 1 - i] 都等于同一个数,则数组 nums 是 互补的 。例如,数组 [1,2,3,4] 是互补的,因为对于所有下标 i ,nums[i] + nums[n - 1 - i] = 5 。

返回使数组 互补 的 最少 操作次数。

示例 1:

输入:nums = [1,2,4,3], limit = 4
输出:1
解释:经过 1 次操作,你可以将数组 nums 变成 [1,2,2,3](加粗元素是变更的数字):
nums[0] + nums[3] = 1 + 3 = 4.
nums[1] + nums[2] = 2 + 2 = 4.
nums[2] + nums[1] = 2 + 2 = 4.
nums[3] + nums[0] = 3 + 1 = 4.
对于每个 i ,nums[i] + nums[n-1-i] = 4 ,所以 nums 是互补的。

示例 2:

输入:nums = [1,2,2,1], limit = 2
输出:2
解释:经过 2 次操作,你可以将数组 nums 变成 [2,2,2,2] 。你不能将任何数字变更为 3 ,因为 3 > limit 。

示例 3:

输入:nums = [1,2,1,2], limit = 2
输出:0
解释:nums 已经是互补的。

说明:

  • n == nums.length
  • 2 <= n <= 10^5
  • 1 <= nums[i] <= limit <= 10^5
  • n 是偶数。

思路

有一个长度为偶数的数组 nums 和一个整数 limit,每次操作可以将任意元素替换为 [1, limit] 中的任意整数,求使得 nums 互补的最少操作次数。所谓互补指 nums[i] + nums[n - 1 - i] 的和都相等。

由于 1 <= nums[i] <= limit,每一对元素和都可以变成 [2, 2 * limit] 中的任意数字。

a = nums[i], b = nums[n - 1 - i], sum = a + b,如果只操作一次,考虑左侧 a -> 1 或者 b -> 1sum 最多变化 max(a, b) - 1;右侧 a -> limit 或者 b -> limitsum 最多变化 limit - min(a, b)

  • sum 变成区间 [sum - (max(a, b) - 1), sum - 1], [sum + 1, sum + limit - min(a, b)] 内的值需要操作一次
  • 变成 sum 的操作次数不变
  • 其它 [2, sum - (max(a, b) - 1) - 1], [sum + limit - min(a, b) + 1, 2 * limit] 需要操作两次

使用差分数组来记录每对元素和变成目标和所需的操作次数,最后遍历所有目标和,取操作次数最小的即可。

代码


/**
 * @date 2026-05-13 10:13
 */
public class MinMoves1674 {

    public int minMoves(int[] nums, int limit) {
        int n = nums.length;
        int max = 2 * limit;
        int[] diff = new int[max + 2];
        for (int i = 0; i < n / 2; i++) {
            int a = nums[i];
            int b = nums[n - 1 - i];
            int sum = a + b;
            int l = sum - (Math.max(a, b) - 1);
            int r = sum + (limit - Math.min(a, b));
            diff[2] += 2;
            diff[Math.max(2, l)]--;
            diff[sum]--;
            diff[Math.min(sum + 1, max + 1)]++;
            diff[Math.min(r + 1, max + 1)]++;
        }
        int sum = 0, res = Integer.MAX_VALUE;
        for (int i = 2; i <= max; i++) {
            sum += diff[i];
            res = Math.min(sum, res);
        }
        return res;
    }

}

性能

3464.正方形上的点之间的最大距离

目标

给你一个整数 side,表示一个正方形的边长,正方形的四个角分别位于笛卡尔平面的 (0, 0) ,(0, side) ,(side, 0) 和 (side, side) 处。

同时给你一个 正整数 k 和一个二维整数数组 points,其中 points[i] = [xi, yi] 表示一个点在正方形边界上的坐标。

你需要从 points 中选择 k 个元素,使得任意两个点之间的 最小 曼哈顿距离 最大化 。

返回选定的 k 个点之间的 最小 曼哈顿距离的 最大 可能值。

两个点 (xi, yi) 和 (xj, yj) 之间的曼哈顿距离为 |xi - xj| + |yi - yj|。

示例 1:

输入: side = 2, points = [[0,2],[2,0],[2,2],[0,0]], k = 4
输出: 2
解释:
选择所有四个点。

示例 2:

输入: side = 2, points = [[0,0],[1,2],[2,0],[2,2],[2,1]], k = 4
输出: 1
解释:
选择点 (0, 0) ,(2, 0) ,(2, 2) 和 (2, 1)。

示例 3:

输入: side = 2, points = [[0,0],[0,1],[0,2],[1,2],[2,0],[2,2],[2,1]], k = 5
输出: 1
解释:
选择点 (0, 0) ,(0, 1) ,(0, 2) ,(1, 2) 和 (2, 2)。

说明:

  • 1 <= side <= 10^9
  • 4 <= points.length <= min(4 side, 15 10^3)
  • points[i] == [xi, yi]
  • 输入产生方式如下:
    • points[i] 位于正方形的边界上。
    • 所有 points[i] 都 互不相同 。
  • 4 <= k <= min(25, points.length)

思路

代码

性能

1855.下标对中的最大距离

目标

给你两个 非递增 的整数数组 nums1 和 nums2 ,数组下标均 从 0 开始 计数。

下标对 (i, j) 中 0 <= i < nums1.length 且 0 <= j < nums2.length 。如果该下标对同时满足 i <= j 且 nums1[i] <= nums2[j] ,则称之为 有效 下标对,该下标对的 距离 为 j - i 。

返回所有 有效 下标对 (i, j) 中的 最大距离 。如果不存在有效下标对,返回 0 。

一个数组 arr ,如果每个 1 <= i < arr.length 均有 arr[i-1] >= arr[i] 成立,那么该数组是一个 非递增 数组。

示例 1:

输入:nums1 = [55,30,5,4,2], nums2 = [100,20,10,10,5]
输出:2
解释:有效下标对是 (0,0), (2,2), (2,3), (2,4), (3,3), (3,4) 和 (4,4) 。
最大距离是 2 ,对应下标对 (2,4) 。

示例 2:

输入:nums1 = [2,2,2], nums2 = [10,10,1]
输出:1
解释:有效下标对是 (0,0), (0,1) 和 (1,1) 。
最大距离是 1 ,对应下标对 (0,1) 。

示例 3:

输入:nums1 = [30,29,19,5], nums2 = [25,25,25,25,25]
输出:2
解释:有效下标对是 (2,2), (2,3), (2,4), (3,3) 和 (3,4) 。
最大距离是 2 ,对应下标对 (2,4) 。

说明:

  • 1 <= nums1.length <= 10^5
  • 1 <= nums2.length <= 10^5
  • 1 <= nums1[i], nums2[j] <= 10^5
  • nums1 和 nums2 都是 非递增 数组

思路

有两个非递增的整数数组 nums1nums2,返回有效下标对的最大距离,如果不存在有效下标对,返回 0。有效下标对 (i, j) 需要满足 i <= j && nums1[i] <= nums2[j]

二分查找 nums2 大于等于 nums1[i] 的最大下标,记录 j - i 的最大值。

代码


/**
 * @date 2026-04-19 23:44
 */
public class MaxDistance1855 {

    public int maxDistance(int[] nums1, int[] nums2) {
        int res = 0;
        for (int i = 0; i < nums1.length; i++) {
            int j = upperBound(nums2, i, nums1[i]);
            res = Math.max(res, j - i);
        }
        return res;
    }

    public int upperBound(int[] arr, int l, int target) {
        int r = arr.length - 1;
        int m = l + (r - l) / 2;
        while (l <= r) {
            if (arr[m] >= target) {
                l = m + 1;
            } else {
                r = m - 1;
            }
            m = l + (r - l) / 2;
        }
        return r;
    }
}

性能

3488.距离最小相等元素查询

目标

给你一个 环形 数组 nums 和一个数组 queries 。

对于每个查询 i ,你需要找到以下内容:

  • 数组 nums 中下标 queries[i] 处的元素与 任意 其他下标 j(满足 nums[j] == nums[queries[i]])之间的 最小 距离。如果不存在这样的下标 j,则该查询的结果为 -1 。

返回一个数组 answer,其大小与 queries 相同,其中 answer[i] 表示查询i的结果。

示例 1:

输入: nums = [1,3,1,4,1,3,2], queries = [0,3,5]
输出: [2,-1,3]
解释:
查询 0:下标 queries[0] = 0 处的元素为 nums[0] = 1 。最近的相同值下标为 2,距离为 2。
查询 1:下标 queries[1] = 3 处的元素为 nums[3] = 4 。不存在其他包含值 4 的下标,因此结果为 -1。
查询 2:下标 queries[2] = 5 处的元素为 nums[5] = 3 。最近的相同值下标为 1,距离为 3(沿着循环路径:5 -> 6 -> 0 -> 1)。

示例 2:

输入: nums = [1,2,3,4], queries = [0,1,2,3]
输出: [-1,-1,-1,-1]
解释:
数组 nums 中的每个值都是唯一的,因此没有下标与查询的元素值相同。所有查询的结果均为 -1。

说明:

  • 1 <= queries.length <= nums.length <= 10^5
  • 1 <= nums[i] <= 10^6
  • 0 <= queries[i] < nums.length

思路

有一个环形数组 nums 和一个查询数组 queries,返回 元素值与 queries[i] 相同的其它元素之间的最小距离,如果不存在则结果为 -1,返回查询结果数组。

根据元素值分组,分组内记录下标。针对每个查询,二分查找元素在分组中的位置,比较前后下标的距离,如果分组只有一个元素,返回 -1。需要特殊处理首与尾的最近距离。

也可以分组后预处理每个位置的最近距离,统一计算前后的最近距离,查询时直接取结果即可。注意循环数组的距离处理,这里直接将超出的距离映射回了原数组下标,因此最近距离需要取 min(d, n - d),其中 d = abs(i - j) 表示下标 ij 的距离。

代码


/**
 * @date 2026-04-16 8:55
 */
public class SolveQueries3488 {

    public List<Integer> solveQueries_v1(int[] nums, int[] queries) {
        Map<Integer, List<Integer>> map = new HashMap<>();
        int n = nums.length;
        for (int i = 0; i < n; i++) {
            map.putIfAbsent(nums[i], new ArrayList<>());
            map.get(nums[i]).add(i);
        }
        int[] dis = new int[n];
        for (List<Integer> list : map.values()) {
            int size = list.size();
            for (int i = 0; i < size; i++) {
                int index = list.get(i);
                if (size >= 2) {
                    int prev = Math.abs(index - list.get((size + i - 1) % size));
                    int next = Math.abs(list.get((i + 1) % size) - index);
                    dis[index] = Math.min(Math.min(prev, n - prev), Math.min(next, n - next));
                } else {
                    dis[index] = -1;
                }
            }
        }
        List<Integer> res = new ArrayList<>(queries.length);
        for (int query : queries) {
            res.add(dis[query]);
        }
        return res;
    }

}

性能

3661.可以被机器人摧毁的最大墙壁数目

目标

一条无限长的直线上分布着一些机器人和墙壁。给你整数数组 robots ,distance 和 walls:

  • robots[i] 是第 i 个机器人的位置。
  • distance[i] 是第 i 个机器人的子弹可以行进的 最大 距离。
  • walls[j] 是第 j 堵墙的位置。

每个机器人有 一颗 子弹,可以向左或向右发射,最远距离为 distance[i] 米。

子弹会摧毁其射程内路径上的每一堵墙。机器人是固定的障碍物:如果子弹在到达墙壁前击中另一个机器人,它会 立即 在该机器人处停止,无法继续前进。

返回机器人可以摧毁墙壁的 最大 数量。

注意:

  • 墙壁和机器人可能在同一位置;该位置的墙壁可以被该位置的机器人摧毁。
  • 机器人不会被子弹摧毁。

示例 1:

输入: robots = [4], distance = [3], walls = [1,10]
输出: 1
解释:
robots[0] = 4 向 左 发射,distance[0] = 3,覆盖范围 [1, 4],摧毁了 walls[0] = 1。
因此,答案是 1。

示例 2:

输入: robots = [10,2], distance = [5,1], walls = [5,2,7]
输出: 3
解释:
robots[0] = 10 向 左 发射,distance[0] = 5,覆盖范围 [5, 10],摧毁了 walls[0] = 5 和 walls[2] = 7。
robots[1] = 2 向 左 发射,distance[1] = 1,覆盖范围 [1, 2],摧毁了 walls[1] = 2。
因此,答案是 3。

示例 3:

输入: robots = [1,2], distance = [100,1], walls = [10]
输出: 0
解释:
在这个例子中,只有 robots[0] 能够到达墙壁,但它向 右 的射击被 robots[1] 挡住了,因此答案是 0。

说明:

  • 1 <= robots.length == distance.length <= 10^5
  • 1 <= walls.length <= 10^5
  • 1 <= robots[i], walls[j] <= 10^9
  • 1 <= distance[i] <= 10^5
  • robots 中的所有值都是 互不相同 的
  • walls 中的所有值都是 互不相同 的

思路

无限长的直线上分布着一些机器人(位于 robots[i])和墙壁 (位于 walls[j]),机器人可以向左或向右发射一枚子弹,位于 robots[i] 的机器人发射的子弹最多可以行进 distance[i]。子弹会摧毁其射程内路径上的每一堵墙,子弹不能摧毁或穿过机器人,求摧毁墙的最大数目。所有机器人的位置都是互不相同的,所有墙的位置也是互不相同的。

根据机器人的位置排序,考虑相邻机器人可以摧毁的墙的数目。定义 dp[i][k] 表示前 i 个机器人摧毁墙的最大数目,且第 i 个机器人朝 k 发射子弹,k = 0 表示向左, k = 1 表示向右。walls(from, to) 表示区间 [from, to] 内的墙的个数。

  • dp[i][0] = max(dp[i - 1][0] + walls(max(prev + 1, cur - dcur), cur), dp[i - 1][1] + walls(max(prev + dprev + 1, cur - dcur), cur))

  • dp[i][1] = max(dp[i - 1][0], dp[i - 1][1]) + walls(cur, min(cur + dcur, next - 1))

  • 当前机器人向左射,需要考虑前一个机器人的射击方向,如果前一个机器人也向左射,只需考虑区间 [max(prev + 1, cur - dcur), cur] 中墙的个数,如果向右射则需要考虑 [max(prev + dprev + 1, cur - dcur), cur] 中墙的个数。其中 dcur 表示位于 cur 的机器人的射击距离,dprev 表示位于 prev 机器人的射击距离。

  • 当前机器人向右射,无需考虑前一个机器人的射击方向,取二者最大的即可,当前机器人向右射的范围是 [cur, min(cur + dcur, next - 1)],注意特殊处理最后一个机器人,没有 next 时取 Integer.MAX_VALUE

剩下的问题是如何快速获取区间内墙的数量,由于不涉及更新,可以直接二分获得上下界。

代码


/**
 * @date 2026-04-03 10:14
 */
public class MaxWalls3661 {

    public int maxWalls(int[] robots, int[] distance, int[] walls) {
        Arrays.sort(walls);
        int n = robots.length;
        Integer[] index = new Integer[n];
        Arrays.setAll(index, i -> i);
        Arrays.sort(index, (a, b) -> robots[a] - robots[b]);
        int[][] dp = new int[n][2];
        Integer first = index[0];
        dp[0][0] = getWalls(walls, robots[first] - distance[first], robots[first]);
        dp[0][1] = getWalls(walls, robots[first], Math.min(robots[first] + distance[first], n > 1 ? robots[index[1]] - 1 : Integer.MAX_VALUE));
        for (int i = 1; i < n; i++) {
            Integer cur = index[i];
            Integer prev = index[i - 1];
            dp[i][0] = Math.max(dp[i - 1][0] + getWalls(walls, Math.max(robots[prev] + 1, robots[cur] - distance[cur]), robots[cur]),
                    dp[i - 1][1] + getWalls(walls, Math.max(robots[cur] - distance[cur], robots[prev] + distance[prev] + 1), robots[cur]));
            dp[i][1] = Math.max(dp[i - 1][0], dp[i - 1][1]) + getWalls(walls, robots[cur], Math.min(robots[cur] + distance[cur], i < n - 1 ? robots[index[i + 1]] - 1 : Integer.MAX_VALUE));
        }
        return Math.max(dp[n - 1][0], dp[n - 1][1]);
    }

    /**
     * 获取 [from, to] 之间的墙的数量
     */
    public int getWalls(int[] walls, int from, int to) {
        int r = upperBound(walls, to);
        int l = lowerBound(walls, from);
        if (r < l) {
            return 0;
        }
        return r - l + 1;
    }

    /**
     * 返回 <= target 的最大下标
     */
    public int upperBound(int[] walls, int target) {
        int l = 0, r = walls.length - 1;
        int m = l + (r - l) / 2;
        while (l <= r) {
            if (walls[m] <= target) {
                l = m + 1;
            } else {
                r = m - 1;
            }
            m = l + (r - l) / 2;
        }
        return r;
    }

    /**
     * 返回 >= target 的最小下标
     */
    public int lowerBound(int[] walls, int target) {
        int l = 0, r = walls.length - 1;
        int m = l + (r - l) / 2;
        while (l <= r) {
            if (walls[m] >= target) {
                r = m - 1;
            } else {
                l = m + 1;
            }
            m = l + (r - l) / 2;
        }
        return l;
    }

}

性能