2411.按位或最大的最小子数组长度

目标

给你一个长度为 n 下标从 0 开始的数组 nums ,数组中所有数字均为非负整数。对于 0 到 n - 1 之间的每一个下标 i ,你需要找出 nums 中一个 最小 非空子数组,它的起始位置为 i (包含这个位置),同时有 最大 的 按位或运算值 。

  • 换言之,令 Bij 表示子数组 nums[i...j] 的按位或运算的结果,你需要找到一个起始位置为 i 的最小子数组,这个子数组的按位或运算的结果等于 max(Bik) ,其中 i <= k <= n - 1 。

一个数组的按位或运算值是这个数组里所有数字按位或运算的结果。

请你返回一个大小为 n 的整数数组 answer,其中 answer[i]是开始位置为 i ,按位或运算结果最大,且 最短 子数组的长度。

子数组 是数组里一段连续非空元素组成的序列。

示例 1:

输入:nums = [1,0,2,1,3]
输出:[3,3,2,2,1]
解释:
任何位置开始,最大按位或运算的结果都是 3 。
- 下标 0 处,能得到结果 3 的最短子数组是 [1,0,2] 。
- 下标 1 处,能得到结果 3 的最短子数组是 [0,2,1] 。
- 下标 2 处,能得到结果 3 的最短子数组是 [2,1] 。
- 下标 3 处,能得到结果 3 的最短子数组是 [1,3] 。
- 下标 4 处,能得到结果 3 的最短子数组是 [3] 。
所以我们返回 [3,3,2,2,1] 。

示例 2:

输入:nums = [1,2]
输出:[2,1]
解释:
下标 0 处,能得到最大按位或运算值的最短子数组长度为 2 。
下标 1 处,能得到最大按位或运算值的最短子数组长度为 1 。
所以我们返回 [2,1] 。

说明:

  • n == nums.length
  • 1 <= n <= 10^5
  • 0 <= nums[i] <= 10^9

思路

有一个数组 nums,求以每个元素为起点的子数组,取得最大或值的最小长度。res[i] 表示以 i 为起点的子数组中,数组元素按位或取得最大值时的最小长度。

枚举右端点,将当前元素与前面的元素进行或运算,并覆盖原来的元素值。对于当前元素 jnums[i] 中存的是 i ~ j 的或值,站在集合的角度来看,nums[i + 1]nums[i] 的子集。在进行或运算时,如果或值没有变大,就没有必要继续向前了。

代码


/**
 * @date 2025-07-29 9:39
 */
public class SmallestSubarrays2411 {

    public int[] smallestSubarrays(int[] nums) {
        int n = nums.length;
        int[] res = new int[n];
        for (int i = 0; i < n; i++) {
            int num = nums[i];
            res[i] = 1;
            for (int j = i - 1; j >= 0 && (num | nums[j]) != nums[j]; j--) {
                nums[j] |= num;
                res[j] = i - j + 1;
            }
        }
        return res;
    }

}

性能

1695.删除子数组的最大得分

目标

给你一个正整数数组 nums ,请你从中删除一个含有 若干不同元素 的子数组。删除子数组的 得分 就是子数组各元素之 和 。

返回 只删除一个 子数组可获得的 最大得分 。

如果数组 b 是数组 a 的一个连续子序列,即如果它等于 a[l],a[l+1],...,a[r] ,那么它就是 a 的一个子数组。

示例 1:

输入:nums = [4,2,4,5,6]
输出:17
解释:最优子数组是 [2,4,5,6]

示例 2:

输入:nums = [5,2,1,2,5,2,1,2,5]
输出:8
解释:最优子数组是 [5,2,1] 或 [1,2,5]

说明:

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

思路

有一个正整数数组 nums,求一个不含重复元素的子数组,使得子数组的元素和最大。

滑动窗口,要求窗口内部不含重复元素,求窗口内元素和的最大值。

代码


/**
 * @date 2025-07-22 8:50
 */
public class MaximumUniqueSubarray1695 {

    public int maximumUniqueSubarray(int[] nums) {
        int n = nums.length;
        Set<Integer> set = new HashSet<>();
        int l = 0;
        int sum = 0;
        int res = 0;
        for (int r = 0; r < n; r++) {
            sum += nums[r];
            while (l < r && set.contains(nums[r])) {
                sum -= nums[l];
                set.remove(nums[l++]);
            }
            set.add(nums[r]);
            res = Math.max(res, sum);
        }
        return res;
    }

}

性能

3439.重新安排会议得到最多空余时间I

目标

给你一个整数 eventTime 表示一个活动的总时长,这个活动开始于 t = 0 ,结束于 t = eventTime 。

同时给你两个长度为 n 的整数数组 startTime 和 endTime 。它们表示这次活动中 n 个时间 没有重叠 的会议,其中第 i 个会议的时间为 [startTime[i], endTime[i]] 。

你可以重新安排 至多 k 个会议,安排的规则是将会议时间平移,且保持原来的 会议时长 ,你的目的是移动会议后 最大化 相邻两个会议之间的 最长 连续空余时间。

移动前后所有会议之间的 相对 顺序需要保持不变,而且会议时间也需要保持互不重叠。

请你返回重新安排会议以后,可以得到的 最大 空余时间。

注意,会议 不能 安排到整个活动的时间以外。

示例 1:

输入:eventTime = 5, k = 1, startTime = [1,3], endTime = [2,5]
输出:2
解释:
将 [1, 2] 的会议安排到 [2, 3] ,得到空余时间 [0, 2] 。

示例 2:

输入:eventTime = 10, k = 1, startTime = [0,2,9], endTime = [1,4,10]
输出:6
解释:
将 [2, 4] 的会议安排到 [1, 3] ,得到空余时间 [3, 9] 。

示例 3:

输入:eventTime = 5, k = 2, startTime = [0,1,2,3,4], endTime = [1,2,3,4,5]
输出:0
解释:
活动中的所有时间都被会议安排满了。

说明:

  • 1 <= eventTime <= 10^9
  • n == startTime.length == endTime.length
  • 2 <= n <= 10^5
  • 1 <= k <= n
  • 0 <= startTime[i] < endTime[i] <= eventTime
  • endTime[i] <= startTime[i + 1] 其中 i 在范围 [0, n - 2] 之间。

思路

有一个活动有 n 个时间不重叠的会议,重新安排 k 个会议议程,使得空余时间最大。

先根据会议时间排序,滑动窗口计算 k 个会议的总时间,以及 [end[l - 1], start[r + 1]],相减即为窗口内的最大空余时间。

代码


/**
 * @date 2025-07-09 8:52
 */
public class MaxFreeTime3439 {

    public int maxFreeTime(int eventTime, int k, int[] startTime, int[] endTime) {
        int n = startTime.length;
        int[][] interval = new int[n + 2][2];
        interval[0][1] = 0;
        for (int i = 1; i <= n; i++) {
            interval[i] = new int[]{startTime[i - 1], endTime[i - 1]};
        }
        interval[n + 1][0] = eventTime;
        int l = 1;
        int res = 0, sum = 0;
        for (int r = 1; r <= n; r++) {
            sum += interval[r][1] - interval[r][0];
            if (r - l == k - 1) {
                res = Math.max(res, interval[r + 1][0] - interval[l - 1][1] - sum);
                sum -= interval[l][1] - interval[l][0];
                l++;
            }
        }
        return res;
    }

}

性能

2200.找出数组中的所有K近邻下标

目标

给你一个下标从 0 开始的整数数组 nums 和两个整数 key 和 k 。K 近邻下标 是 nums 中的一个下标 i ,并满足至少存在一个下标 j 使得 |i - j| <= k 且 nums[j] == key 。

以列表形式返回按 递增顺序 排序的所有 K 近邻下标。

示例 1:

输入:nums = [3,4,9,1,3,9,5], key = 9, k = 1
输出:[1,2,3,4,5,6]
解释:因此,nums[2] == key 且 nums[5] == key 。
- 对下标 0 ,|0 - 2| > k 且 |0 - 5| > k ,所以不存在 j 使得 |0 - j| <= k 且 nums[j] == key 。所以 0 不是一个 K 近邻下标。
- 对下标 1 ,|1 - 2| <= k 且 nums[2] == key ,所以 1 是一个 K 近邻下标。
- 对下标 2 ,|2 - 2| <= k 且 nums[2] == key ,所以 2 是一个 K 近邻下标。
- 对下标 3 ,|3 - 2| <= k 且 nums[2] == key ,所以 3 是一个 K 近邻下标。
- 对下标 4 ,|4 - 5| <= k 且 nums[5] == key ,所以 4 是一个 K 近邻下标。
- 对下标 5 ,|5 - 5| <= k 且 nums[5] == key ,所以 5 是一个 K 近邻下标。
- 对下标 6 ,|6 - 5| <= k 且 nums[5] == key ,所以 6 是一个 K 近邻下标。
因此,按递增顺序返回 [1,2,3,4,5,6] 。 

示例 2:

输入:nums = [2,2,2,2,2], key = 2, k = 2
输出:[0,1,2,3,4]
解释:对 nums 的所有下标 i ,总存在某个下标 j 使得 |i - j| <= k 且 nums[j] == key ,所以每个下标都是一个 K 近邻下标。 
因此,返回 [0,1,2,3,4] 。

说明:

  • 1 <= nums.length <= 1000
  • 1 <= nums[i] <= 1000
  • key 是数组 nums 中的一个整数
  • 1 <= k <= nums.length

思路

返回数组中元素值为 keyk 临近下标,即元素 key 的下标以及其左右 k 个下标。要求以递增顺序返回,不能包含重复下标。

遍历数组,判断 nums[i] 是否等于 k,如果相等则将左右两侧的 k 个下标加入答案。使用一个指针标记当前已经记录的最大下标,避免将重复的下标加入答案。

代码


/**
 * @date 2025-06-24 0:11
 */
public class FindKDistantIndices2200 {

    public List<Integer> findKDistantIndices(int[] nums, int key, int k) {
        List<Integer> res = new ArrayList<>();
        int n = nums.length;
        int r = -1;
        for (int i = 0; i < n; i++) {
            if (nums[i] == key) {
                int l = Math.max(r + 1, i - k);
                r = Math.min(n - 1, i + k);
                for (int j = l; j <= r; j++) {
                    res.add(j);
                }
            }
        }
        return res;
    }

}

性能

3445.奇偶频次间的最大差值II

目标

给你一个字符串 s 和一个整数 k 。请你找出 s 的子字符串 subs 中两个字符的出现频次之间的 最大 差值,freq[a] - freq[b] ,其中:

  • subs 的长度 至少 为 k 。
  • 字符 a 在 subs 中出现奇数次。
  • 字符 b 在 subs 中出现偶数次。

返回 最大 差值。

注意 ,subs 可以包含超过 2 个 互不相同 的字符。.

子字符串 是字符串中的一个连续字符序列。

示例 1:

输入:s = "12233", k = 4
输出:-1
解释:
对于子字符串 "12233" ,'1' 的出现次数是 1 ,'3' 的出现次数是 2 。差值是 1 - 2 = -1 。

示例 2:

输入:s = "1122211", k = 3
输出:1
解释:
对于子字符串 "11222" ,'2' 的出现次数是 3 ,'1' 的出现次数是 2 。差值是 3 - 2 = 1 。

示例 3:

输入:s = "110", k = 3
输出:-1

说明:

  • 3 <= s.length <= 3 * 10^4
  • s 仅由数字 '0' 到 '4' 组成。
  • 输入保证至少存在一个子字符串是由一个出现奇数次的字符和一个出现偶数次的字符组成。
  • 1 <= k <= s.length

思路

有一个字符串 s 仅由 0 ~ 4 组成,求其长度至少为 k 的子串中,3442_奇偶频次间的最大差值I 的最大值。

// todo

代码

性能

1550.存在连续三个奇数的数组

目标

给你一个整数数组 arr,请你判断数组中是否存在连续三个元素都是奇数的情况:如果存在,请返回 true ;否则,返回 false 。

示例 1:

输入:arr = [2,6,4,1]
输出:false
解释:不存在连续三个元素都是奇数的情况。

示例 2:

输入:arr = [1,2,34,3,4,5,7,23,12]
输出:true
解释:存在连续三个元素都是奇数的情况,即 [5,7,23] 。

说明:

  • 1 <= arr.length <= 1000
  • 1 <= arr[i] <= 1000

思路

判断数组中是否存在三个连续的奇数。

使用定长滑动窗口。

代码


/**
 * @date 2025-05-11 0:15
 */
public class ThreeConsecutiveOdds1550 {

    public boolean threeConsecutiveOdds(int[] arr) {
        int n = arr.length;
        int left = 0;
        for (int right = 0; right < n; right++) {
            if (arr[right] % 2 == 0) {
                left = right + 1;
                continue;
            }
            if (right - left == 2) {
                return true;
            }
        }
        return false;
    }

}

性能

2962.统计最大元素出现至少K次的子数组

目标

给你一个整数数组 nums 和一个 正整数 k 。

请你统计有多少满足 「nums 中的 最大 元素」至少出现 k 次的子数组,并返回满足这一条件的子数组的数目。

子数组是数组中的一个连续元素序列。

示例 1:

输入:nums = [1,3,2,3,3], k = 2
输出:6
解释:包含元素 3 至少 2 次的子数组为:[1,3,2,3]、[1,3,2,3,3]、[3,2,3]、[3,2,3,3]、[2,3,3] 和 [3,3] 。

示例 2:

输入:nums = [1,4,2,1], k = 3
输出:0
解释:没有子数组包含元素 4 至少 3 次。

说明:

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

思路

找出满足条件的子数组个数,子数组至少要包含 k 个最大元素。

代码


/**
 * @date 2025-04-29 8:50
 */
public class CountSubarrays2962 {

    public long countSubarrays(int[] nums, int k) {
        long res = 0L;
        int max = Arrays.stream(nums).max().orElse(1);
        int left = 0;
        for (int right = 0; right < nums.length; right++) {
            k -= nums[right] == max ? 1 : 0;
            while (k <= 0){
                k += nums[left++] == max ? 1 : 0;
            }
            res += left;
        }
        return res;
    }
}

性能

2302.统计得分小于K的子数组数目

目标

一个数组的 分数 定义为数组之和 乘以 数组的长度。

  • 比方说,[1, 2, 3, 4, 5] 的分数为 (1 + 2 + 3 + 4 + 5) * 5 = 75 。

给你一个正整数数组 nums 和一个整数 k ,请你返回 nums 中分数 严格小于 k 的 非空整数子数组数目。

子数组 是数组中的一个连续元素序列。

示例 1:

输入:nums = [2,1,4,3,5], k = 10
输出:6
解释:
有 6 个子数组的分数小于 10 :
- [2] 分数为 2 * 1 = 2 。
- [1] 分数为 1 * 1 = 1 。
- [4] 分数为 4 * 1 = 4 。
- [3] 分数为 3 * 1 = 3 。 
- [5] 分数为 5 * 1 = 5 。
- [2,1] 分数为 (2 + 1) * 2 = 6 。
注意,子数组 [1,4] 和 [4,3,5] 不符合要求,因为它们的分数分别为 10 和 36,但我们要求子数组的分数严格小于 10 。

示例 2:

输入:nums = [1,1,1], k = 5
输出:5
解释:
除了 [1,1,1] 以外每个子数组分数都小于 5 。
[1,1,1] 分数为 (1 + 1 + 1) * 3 = 9 ,大于 5 。
所以总共有 5 个子数组得分小于 5 。

说明:

  • 1 <= nums.length <= 10^5
  • 1 <= nums[i] <= 10^5
  • 1 <= k <= 10^15

思路

统计 正整数 数组中 子数组元素和 * 子数组长度 < k 的子数组个数。

统计满足条件的子数组个数,首先考虑滑动窗口。枚举右端点 right,只要 [left, right] 的分数小于 k,那么任意以 i ∈ [left, right] 为左端点的子数组的分数都小于 k,共有 right - left + 1 个。如果窗口内的分数大于等于 k,需要移出左端点,直到窗口内的分数小于 k。

代码


/**
 * @date 2025-04-28 8:41
 */
public class CountSubarrays2302 {

    public long countSubarrays(int[] nums, long k) {
        long res = 0L;
        int n = nums.length;
        int left = 0;
        long sum = 0L;
        int len = 0;
        for (int right = 0; right < n; right++) {
            sum += nums[right];
            len++;
            while (left < n && sum * len >= k) {
                sum -= nums[left++];
                len--;
            }
            res += right - left + 1;
        }
        return res;
    }

}

性能

2444.统计定界子数组的数目

目标

给你一个整数数组 nums 和两个整数 minK 以及 maxK 。

nums 的定界子数组是满足下述条件的一个子数组:

  • 子数组中的 最小值 等于 minK 。
  • 子数组中的 最大值 等于 maxK 。

返回定界子数组的数目。

子数组是数组中的一个连续部分。

示例 1:

输入:nums = [1,3,5,2,7,5], minK = 1, maxK = 5
输出:2
解释:定界子数组是 [1,3,5] 和 [1,3,5,2] 。

示例 2:

输入:nums = [1,1,1,1], minK = 1, maxK = 1
输出:10
解释:nums 的每个子数组都是一个定界子数组。共有 10 个子数组。

说明:

  • 2 <= nums.length <= 10^5
  • 1 <= nums[i], minK, maxK <= 10^6

思路

统计满足条件的子数组个数,子数组中必须包含 minKmaxK,并且其它元素值在 [minK, maxK] 内。

明显的滑动窗口问题,求窗口内 minKmaxK 的计数均大于等于 1 且元素值在 [minK, maxK] 内的子数组个数。

如果遇到不在范围内的元素,直接从下一个位置重新开始滑动,关键点是记录滑动的开始位置 start

代码


/**
 * @date 2025-04-26 0:25
 */
public class CountSubarrays2444 {

    public long countSubarrays(int[] nums, int minK, int maxK) {
        int n = nums.length;
        int minCnt = 0, maxCnt = 0;
        int left = 0, start = 0;
        long res = 0L;
        for (int right = 0; right < n; right++) {
            if (nums[right] < minK || nums[right] > maxK) {
                left = right + 1;
                start = left;
                minCnt = 0;
                maxCnt = 0;
                continue;
            }
            if (nums[right] == minK) {
                minCnt++;
            }
            if (nums[right] == maxK) {
                maxCnt++;
            }
            while (minCnt > 0 && maxCnt > 0) {
                if (nums[left] == minK) {
                    minCnt--;
                }
                if (nums[left] == maxK) {
                    maxCnt--;
                }
                left++;
            }
            res += left - start;
        }
        return res;
    }

}

性能

2799.统计完全子数组的数目

目标

给你一个由 正 整数组成的数组 nums 。

如果数组中的某个子数组满足下述条件,则称之为 完全子数组 :

  • 子数组中 不同 元素的数目等于整个数组不同元素的数目。

返回数组中 完全子数组 的数目。

子数组 是数组中的一个连续非空序列。

示例 1:

输入:nums = [1,3,1,2,2]
输出:4
解释:完全子数组有:[1,3,1,2]、[1,3,1,2,2]、[3,1,2] 和 [3,1,2,2] 。

示例 2:

输入:nums = [5,5,5,5]
输出:10
解释:数组仅由整数 5 组成,所以任意子数组都满足完全子数组的条件。子数组的总数为 10 。

说明:

  • 1 <= nums.length <= 1000
  • 1 <= nums[i] <= 2000

思路

求数组的完全子数组数目,完全子数组指包含数组中的所有不同元素的子数组。

使用哈希集合统计数组中的不同元素个数,使用滑动窗口统计符合条件的子数组数目。初始化时,记录窗口内元素的出现次数,直到包含所有不同元素。循环内如果缺少元素种类则扩展右边界,然后收缩左边界,直到移出元素的出现次数降为 0

代码


/**
 * @date 2025-04-24 0:53
 */
public class CountCompleteSubarrays2799 {

    public int countCompleteSubarrays(int[] nums) {
        Set<Integer> set = new HashSet<>();
        int max = 0;
        for (int num : nums) {
            set.add(num);
            max = Math.max(max, num);
        }
        int kinds = set.size();
        int left = 0, right = 0;
        set.clear();
        int[] cnt = new int[max + 1];
        while (set.size() < kinds) {
            cnt[nums[right]]++;
            set.add(nums[right++]);
        }
        int n = nums.length;
        int res = 0;
        int out = nums[left];
        do {
            while (right < n && cnt[out] == 0) {
                cnt[nums[right++]]++;
            }
            while (left < n && cnt[out] > 0) {
                res += n - right + 1;
                out = nums[left];
                cnt[nums[left++]]--;
            }
        } while (right < n);
        return res;
    }

}

性能