2089.找出数组排序后的目标下标

目标

给你一个下标从 0 开始的整数数组 nums 以及一个目标元素 target 。

目标下标 是一个满足 nums[i] == target 的下标 i 。

将 nums 按 非递减 顺序排序后,返回由 nums 中目标下标组成的列表。如果不存在目标下标,返回一个 空 列表。返回的列表必须按 递增 顺序排列。

示例 1:

输入:nums = [1,2,5,2,3], target = 2
输出:[1,2]
解释:排序后,nums 变为 [1,2,2,3,5] 。
满足 nums[i] == 2 的下标是 1 和 2 。

示例 2:

输入:nums = [1,2,5,2,3], target = 3
输出:[3]
解释:排序后,nums 变为 [1,2,2,3,5] 。
满足 nums[i] == 3 的下标是 3 。

示例 3:

输入:nums = [1,2,5,2,3], target = 5
输出:[4]
解释:排序后,nums 变为 [1,2,2,3,5] 。
满足 nums[i] == 5 的下标是 4 。

示例 4:

输入:nums = [1,2,5,2,3], target = 4
输出:[]
解释:nums 中不含值为 4 的元素。

说明:

  • 1 <= nums.length <= 100
  • 1 <= nums[i], target <= 100

思路

有一个下标从 0 开始的数组 nums,从中查找值为 target 的元素,返回其在排序后的数组中的下标,如果没有找到返回空。

直接的想法是先排序数组,然后遍历查找目标,将下标加入结果集,时间复杂度为 O(nlogn)。

本题只关心特定元素在数组中的位次,只需统计左右两端的元素数目即可算出下标。

代码


/**
 * @date 2026-10-19 8:48
 */
public class TargetIndices2089 {

    public List<Integer> targetIndices(int[] nums, int target) {
        int n = nums.length;
        int l = 0, r = 0;
        List<Integer> res = new ArrayList<>();
        for (int num : nums) {
            if (num < target) {
                l++;
            } else if (num > target) {
                r++;
            }
        }
        int length = n - l - r;
        for (int i = 0; i < length; i++) {
            res.add(l++);
        }
        return res;
    }
}

性能

1658.将x减到0的最小操作数

目标

给你一个整数数组 nums 和一个整数 x 。每一次操作时,你应当移除数组 nums 最左边或最右边的元素,然后从 x 中减去该元素的值。请注意,需要 修改 数组以供接下来的操作使用。

如果可以将 x 恰好 减到 0 ,返回 最小操作数 ;否则,返回 -1 。

示例 1:

输入:nums = [1,1,4,2,3], x = 5
输出:2
解释:最佳解决方案是移除后两个元素,将 x 减到 0 。

示例 2:

输入:nums = [5,6,7,8,9], x = 4
输出:-1

示例 3:

输入:nums = [3,2,20,1,1,3], x = 10
输出:5
解释:最佳解决方案是移除后三个元素和前两个元素(总共 5 次操作),将 x 减到 0 。

说明:

  • 1 <= nums.length <= 10^5
  • 1 <= nums[i] <= 10^4
  • 1 <= x <= 10^9

思路

有一个正整数数组 nums,每一次操作可以移除剩余数组的最左或最右元素,求移除元素和等于 x 的最小操作数,如果不存在则返回 -1。

将问题转化为求子数组和为 sum - x 的最大长度,可以使用滑动窗口。

代码


/**
 * @date 2026-09-23 8:49
 */
public class MinOperations1658 {

    public int minOperations(int[] nums, int x) {
        int n = nums.length;
        int sum = 0;
        for (int num : nums) {
            sum += num;
        }
        if (sum < x) {
            return -1;
        } else if (sum == x) {
            return n;
        }
        int target = sum - x;
        int res = 0;
        int l = 0;
        sum = 0;
        for (int r = 0; r < n; r++) {
            sum += nums[r];
            while (sum > target) {
                sum -= nums[l++];
            }
            if (sum == target) {
                res = Math.max(res, r - l + 1);
            }
        }
        return res == 0 ? -1 : n - res;
    }

}

性能

1520.最多的不重叠子字符串

目标

给你一个只包含小写字母的字符串 s ,你需要找到 s 中最多数目的非空子字符串,满足如下条件:

  1. 这些字符串之间互不重叠,也就是说对于任意两个子字符串 s[i..j] 和 s[x..y] ,要么 j < x 要么 i > y 。
  2. 如果一个子字符串包含字符 char ,那么 s 中所有 char 字符都应该在这个子字符串中。

请你找到满足上述条件的最多子字符串数目。如果有多个解法有相同的子字符串数目,请返回这些子字符串总长度最小的一个解。可以证明最小总长度解是唯一的。

请注意,你可以以 任意 顺序返回最优解的子字符串。

示例 1:

输入:s = "adefaddaccc"
输出:["e","f","ccc"]
解释:下面为所有满足第二个条件的子字符串:
[
  "adefaddaccc"
  "adefadda",
  "ef",
  "e",
  "f",
  "ccc",
]
如果我们选择第一个字符串,那么我们无法再选择其他任何字符串,所以答案为 1 。如果我们选择 "adefadda" ,剩下子字符串中我们只可以选择 "ccc" ,它是唯一不重叠的子字符串,所以答案为 2 。同时我们可以发现,选择 "ef" 不是最优的,因为它可以被拆分成 2 个子字符串。所以最优解是选择 ["e","f","ccc"] ,答案为 3 。不存在别的相同数目子字符串解。

示例 2:

输入:s = "abbaccd"
输出:["d","bb","cc"]
解释:注意到解 ["d","abba","cc"] 答案也为 3 ,但它不是最优解,因为它的总长度更长。

说明:

  • 1 <= s.length <= 10^5
  • s 只包含小写英文字母。

思路

代码

性能

1477.找两个和为目标值且不重叠的子数组

目标

给你一个整数数组 arr 和一个整数值 target 。

请你在 arr 中找 两个互不重叠的子数组 且它们的和都等于 target 。可能会有多种方案,请你返回满足要求的两个子数组长度和的 最小值 。

请返回满足要求的最小长度和,如果无法找到这样的两个子数组,请返回 -1 。

示例 1:

输入:arr = [3,2,2,4,3], target = 3
输出:2
解释:只有两个子数组和为 3 ([3] 和 [3])。它们的长度和为 2 。

示例 2:

输入:arr = [7,3,4,7], target = 7
输出:2
解释:尽管我们有 3 个互不重叠的子数组和为 7 ([7], [3,4] 和 [7]),但我们会选择第一个和第三个子数组,因为它们的长度和 2 是最小值。

示例 3:

输入:arr = [4,3,2,6,2,3,4], target = 6
输出:-1
解释:我们只有一个和为 6 的子数组。

示例 4:

输入:arr = [5,5,4,4,5], target = 3
输出:-1
解释:我们无法找到和为 3 的子数组。

示例 5:

输入:arr = [3,1,1,1,5,1,2,1], target = 3
输出:3
解释:注意子数组 [1,2] 和 [2,1] 不能成为一个方案因为它们重叠了。

说明:

  • 1 <= arr.length <= 10^5
  • 1 <= arr[i] <= 1000
  • 1 <= target <= 10^8

思路

有一个整数数组 arr 和一个整数值 target,从数组中找到两个不重叠的子数组,使得子数组的和为 target,返回这两个子数组长度之和的最小值。

将重叠问题进行前后缀分解,在各自的部分只需考虑和为 target 的最小长度,最小长度可以使用滑动窗口。

代码


/**
 * @date 2026-09-17 10:06
 */
public class MinSumOfLengths1477 {

    public int minSumOfLengths(int[] arr, int target) {
        int n = arr.length;
        int[] pre = new int[n + 1];
        int[] suf = new int[n + 1];
        Arrays.fill(pre, Integer.MAX_VALUE);
        Arrays.fill(suf, Integer.MAX_VALUE);
        int sum = 0;
        int l = 0;
        for (int i = 0; i < n; i++) {
            sum += arr[i];
            while (sum > target) {
                sum -= arr[l++];
            }
            if (sum == target) {
                pre[i + 1] = Math.min(pre[i], i - l + 1);
            } else {
                pre[i + 1] = pre[i];
            }
        }
        sum = 0;
        int r = n - 1;
        for (int i = n - 1; i >= 0; i--) {
            sum += arr[i];
            while (sum > target) {
                sum -= arr[r--];
            }
            if (sum == target) {
                suf[i] = Math.min(suf[i + 1], r - i + 1);
            } else {
                suf[i] = suf[i + 1];
            }
        }
        int res = Integer.MAX_VALUE;
        for (int i = 0; i < n; i++) {
            if (pre[i + 1] != Integer.MAX_VALUE && suf[i + 1] != Integer.MAX_VALUE) {
                res = Math.min(res, pre[i + 1] + suf[i + 1]);
            }
        }
        return res == Integer.MAX_VALUE ? -1 : res;
    }

}

性能

3414.不重叠区间的最大得分

目标

给你一个二维整数数组 intervals,其中 intervals[i] = [li, ri, weighti]。区间 i 的起点为 li,终点为 ri,权重为 weighti。你最多可以选择 4 个互不重叠 的区间。所选择区间的 得分 定义为这些区间权重的总和。

返回一个至多包含 4 个下标且 字典序最小 的数组,表示从 intervals 中选中的互不重叠且得分最大的区间。

如果两个区间没有任何重叠点,则称二者 互不重叠 。特别地,如果两个区间共享左边界或右边界,也认为二者重叠。

示例 1:

输入: intervals = [[1,3,2],[4,5,2],[1,5,5],[6,9,3],[6,7,1],[8,9,1]]
输出: [2,3]
解释:
可以选择下标为 2 和 3 的区间,其权重分别为 5 和 3。

示例 2:

输入: intervals = [[5,8,1],[6,7,7],[4,7,3],[9,10,6],[7,8,2],[11,14,3],[3,5,5]]
输出: [1,3,5,6]
解释:
可以选择下标为 1、3、5 和 6 的区间,其权重分别为 7、6、3 和 5。

说明:

  • 1 <= intervals.length <= 5 * 10^4
  • intervals[i].length == 3
  • intervals[i] = [li, ri, weighti]
  • 1 <= li <= ri <= 10^9
  • 1 <= weighti <= 10^9

思路

// todo

代码

性能

3501.操作后最大活跃区段数II

目标

给你一个长度为 n 的二进制字符串 s ,其中:

  • '1' 表示一个 活跃 区段。
  • '0' 表示一个 非活跃 区段。

你最多可以进行一次 操作 来最大化 s 中活跃区段的数量。在一次操作中,你可以:

  • 将一个被 '0' 包围的连续 '1' 区块转换为全 '0'。
  • 然后,将一个被 '1' 包围的连续 '0' 区块转换为全 '1'。

此外,你还有一个 二维数组 queries,其中 queries[i] = [li, ri] 表示子字符串 s[li...ri]。

对于每个查询,确定在对子字符串 s[li...ri] 进行最优交换后,字符串 s 中 可能的最大 活跃区段数。

返回一个数组 answer,其中 answer[i] 是 queries[i] 的结果。

注意

  • 对于每个查询,仅对 s[li...ri] 处理时,将其看作是在两端都加上一个 '1' 后的字符串,形成 t = '1' + s[li...ri] + '1'。这些额外的 '1' 不会对最终的活跃区段数有贡献。
  • 各个查询相互独立。

示例 1:

输入: s = "01", queries = [[0,1]]
输出: [1]
解释:

因为没有被 '0' 包围的 '1' 区块,所以没有有效的操作可以进行。最大活跃区段数是 1。

示例 2:

输入: s = "0100", queries = [[0,3],[0,2],[1,3],[2,3]]
输出: [4,3,1,1]
解释:

查询 [0, 3] → 子字符串 "0100" → 变为 "101001"
选择 "0100","0100" → "0000" → "1111"。
最终字符串(去掉添加的 '1')为 "1111"。最大活跃区段数为 4。

查询 [0, 2] → 子字符串 "010" → 变为 "10101"
选择 "010","010" → "000" → "111"。
最终字符串(去掉添加的 '1')为 "1110"。最大活跃区段数为 3。

查询 [1, 3] → 子字符串 "100" → 变为 "11001"
因为没有被 '0' 包围的 '1' 区块,所以没有有效的操作可以进行。最大活跃区段数为 1。

查询 [2, 3] → 子字符串 "00" → 变为 "1001"
因为没有被 '0' 包围的 '1' 区块,所以没有有效的操作可以进行。最大活跃区段数为 1。

示例 3:

输入: s = "1000100", queries = [[1,5],[0,6],[0,4]]
输出: [6,7,2]
解释:

查询 [1, 5] → 子字符串 "00010" → 变为 "1000101"
选择 "00010","00010" → "00000" → "11111"。
最终字符串(去掉添加的 '1')为 "1111110"。最大活跃区段数为 6。

查询 [0, 6] → 子字符串 "1000100" → 变为 "110001001"
选择 "000100","000100" → "000000" → "111111"。
最终字符串(去掉添加的 '1')为 "1111111"。最大活跃区段数为 7。

查询 [0, 4] → 子字符串 "10001" → 变为 "1100011"
因为没有被 '0' 包围的 '1' 区块,所以没有有效的操作可以进行。最大活跃区段数为 2。

示例 4:

输入: s = "01010", queries = [[0,3],[1,4],[1,3]]
输出: [4,4,2]
解释:

查询 [0, 3] → 子字符串 "0101" → 变为 "101011"
选择 "010","010" → "000" → "111"。
最终字符串(去掉添加的 '1')为 "11110"。最大活跃区段数为 4。

查询 [1, 4] → 子字符串 "1010" → 变为 "110101"
选择 "010","010" → "000" → "111"。
最终字符串(去掉添加的 '1')为 "01111"。最大活跃区段数为 4。

查询 [1, 3] → 子字符串 "101" → 变为 "11011"
因为没有被 '0' 包围的 '1' 区块,所以没有有效的操作可以进行。最大活跃区段数为 2。

说明:

  • 1 <= n == s.length <= 10^5
  • 1 <= queries.length <= 10^5
  • s[i] 只有 '0' 或 '1'。
  • queries[i] = [li, ri]
  • 0 <= li <= ri < n

思路

// todo

代码

性能

1331.数组序号转换

目标

给你一个整数数组 arr ,请你将数组中的每个元素替换为它们排序后的序号。

序号代表了一个元素有多大。序号编号的规则如下:

  • 序号从 1 开始编号。
  • 一个元素越大,那么序号越大。如果两个元素相等,那么它们的序号相同。
  • 每个数字的序号都应该尽可能地小。

示例 1:

输入:arr = [40,10,20,30]
输出:[4,1,2,3]
解释:40 是最大的元素。 10 是最小的元素。 20 是第二小的数字。 30 是第三小的数字。

示例 2:

输入:arr = [100,100,100]
输出:[1,1,1]
解释:所有元素有相同的序号。

示例 3:

输入:arr = [37,12,28,9,100,56,80,5,12]
输出:[5,3,4,2,8,6,7,1,3]

说明:

  • 0 <= arr.length <= 10^5
  • -10^9 <= arr[i] <= 10^9

思路

有一个整数数组 arr,返回每个元素排序后的序号,序号从 1 开始,如果元素值相同那么序号也相同,并且后面的序号无需考虑前面重复的元素,比如 [10 20 20 30],返回 [1, 2, 2, 3] 而不是 [1, 2, 2, 4]。

排序 arr 的下标数组 index,根据排序后的 index 返填原数组的序号,注意去重即可。

代码


/**
 * @date 2026-07-14 10:11
 */
public class ArrayRankTransform1331 {

    public int[] arrayRankTransform(int[] arr) {
        int n = arr.length;
        Integer[] index = new Integer[n];
        Arrays.setAll(index, i -> i);
        Arrays.sort(index, (a, b) -> (arr[a] - arr[b]));
        int prev = Integer.MIN_VALUE;
        int sort = 0;
        for (int i = 0; i < n; i++) {
            if (prev != arr[index[i]]){
                sort++;
            }
            prev = arr[index[i]];
            arr[index[i]] = sort;
        }
        return arr;
    }

}

性能

3532.针对图的路径存在性查询I

目标

给你一个整数 n,表示图中的节点数量,这些节点按从 0 到 n - 1 编号。

同时给你一个长度为 n 的整数数组 nums,该数组按 非递减 顺序排序,以及一个整数 maxDiff。

如果满足 |nums[i] - nums[j]| <= maxDiff(即 nums[i] 和 nums[j] 的 绝对差 至多为 maxDiff),则节点 i 和节点 j 之间存在一条 无向边 。

此外,给你一个二维整数数组 queries。对于每个 queries[i] = [ui, vi],需要判断节点 ui 和 vi 之间是否存在路径。

返回一个布尔数组 answer,其中 answer[i] 等于 true 表示在第 i 个查询中节点 ui 和 vi 之间存在路径,否则为 false。

示例 1:

输入: n = 2, nums = [1,3], maxDiff = 1, queries = [[0,0],[0,1]]
输出: [true,false]
解释:
查询 [0,0]:节点 0 有一条到自己的显然路径。
查询 [0,1]:节点 0 和节点 1 之间没有边,因为 |nums[0] - nums[1]| = |1 - 3| = 2,大于 maxDiff。
因此,在处理完所有查询后,最终答案为 [true, false]。

示例 2:

输入: n = 4, nums = [2,5,6,8], maxDiff = 2, queries = [[0,1],[0,2],[1,3],[2,3]]
输出: [false,false,true,true]
解释:
生成的图如下:
查询 [0,1]:节点 0 和节点 1 之间没有边,因为 |nums[0] - nums[1]| = |2 - 5| = 3,大于 maxDiff。
查询 [0,2]:节点 0 和节点 2 之间没有边,因为 |nums[0] - nums[2]| = |2 - 6| = 4,大于 maxDiff。
查询 [1,3]:节点 1 和节点 3 之间存在路径通过节点 2,因为 |nums[1] - nums[2]| = |5 - 6| = 1 和 |nums[2] - nums[3]| = |6 - 8| = 2,都小于等于 maxDiff。
查询 [2,3]:节点 2 和节点 3 之间有一条边,因为 |nums[2] - nums[3]| = |6 - 8| = 2,等于 maxDiff。
因此,在处理完所有查询后,最终答案为 [false, false, true, true]。

说明:

  • 1 <= n == nums.length <= 10^5
  • 0 <= nums[i] <= 10^5
  • nums 按 非递减 顺序排序。
  • 0 <= maxDiff <= 10^5
  • 1 <= queries.length <= 10^5
  • queries[i] == [ui, vi]
  • 0 <= ui, vi < n

思路

有 n 个节点编号为 0 ~ n - 1,节点之间的边由非递减数组 nums 与变量 maxDiff 给出,如果两个节点的差值不超过 maxDiff 则表示它们之间有一条无向边。有一个查询数组 queries,返回每次查询的两个节点是否连通。

由于 nums 非递减,只需考虑相邻节点是否连通即可。如果相邻节点不连通,那么跨过这对节点的区间都不连通。可以使用并查集维护连通性。

代码


/**
 * @date 2026-07-09 8:50
 */
public class PathExistenceQueries3532 {

    class UnionFind {
        private final int[] fa;

        public UnionFind(int n) {
            fa = new int[n];
            Arrays.setAll(fa, i -> i);
        }

        public int find(int i) {
            if (i != fa[i]) {
                fa[i] = find(fa[i]);
            }
            return fa[i];
        }

        public void union(int a, int b) {
            int x = find(a);
            int y = find(b);
            if (x < y) {
                fa[y] = x;
            } else if (x > y) {
                fa[x] = y;
            }
        }
    }

    public boolean[] pathExistenceQueries(int n, int[] nums, int maxDiff, int[][] queries) {
        UnionFind uf = new UnionFind(n);
        for (int i = 1; i < n; i++) {
            if (Math.abs(nums[i] - nums[i - 1]) <= maxDiff) {
                uf.union(i, i - 1);
            }
        }
        boolean[] res = new boolean[queries.length];
        for (int i = 0; i < queries.length; i++) {
            res[i] = uf.find(queries[i][0]) == uf.find(queries[i][1]);
        }
        return res;
    }
}

性能

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

}

性能