题目描述

给定长度为 n 的整数数组 nums。对于每个窗口长度 k(1≤k≤n),考虑所有完整的长度为 k 的连续窗口,求同时出现在这些窗口中的最大整数;不存在时返回 -1。

返回长度为 n 的数组 answer,其中 answer[k-1] 对应窗口长度 k。

示例 1:

输入: nums = [1,3,2,5,3,1]
输出: [-1,-1,3,5,5,5]
解释: 长度 3 的所有窗口都有 3;长度至少 4 时,所有窗口都有 5。

示例 2:

输入: nums = [2,2,2]
输出: [2,2,2]
解释: 每一种长度的每个窗口都含有 2。

提示:

  • 1≤n≤10⁵,元素为 32 位有符号整数。
  • 相同整数在一个窗口中出现一次即可,不要求每个窗口出现次数相等。

题意分析

目标不是分别计算每个窗口的最大值,而是找一个能在同长度所有窗口中出现的共同元素。对每个值先求“窗口至少多长才无法避开它”,再按长度汇总候选,可以避免逐长度逐窗口求交集。

这个最短窗口长度由该值出现位置之间的最大空白段决定;一旦某个长度满足要求,所有更长窗口也满足,因此最后可以用前缀最大值统一得到答案。

解法:按出现位置最大间隔归桶

核心思路

[!blue]

换一个问题问:对固定值 x,窗口至少多长,才能保证每个窗口都碰到一次 x?这取决于最长的“不含 x”的空白段,不需要逐个维护窗口最大值。

将 x 的出现位置两端补上 -1 和 n。相邻位置相差 d 时,中间恰有 d - 1 个位置不含 x;取所有间隔的最大值后,长度小于 d 的窗口可以完全落进空白段,长度至少为 d 的窗口则无法避开 x。

代码中 stats[x][0] 保存最近位置,stats[x][1] 保存目前最大间隔;扫描结束再补上末尾间隔。bucket[d] 记录从长度 d 开始符合条件的最大值,之后求前缀最大值,就得到每种窗口长度的答案。has、found 单独表示是否存在候选,不能让初始的 0 压过合法负数。

例如长度为 5 的数组中,x 出现在下标 1、4,补边界后位置为 -1、1、4、5,最大间隔为 3:长度为 2 的窗口可能落在下标 2、3,长度达到 3 后就无法完全避开 x。

解题步骤

  1. 为每个值记录上次位置和最大相邻出现间隔,边界补 -1 与 n。
  2. 最大间隔 d 表示该值从窗口长度 d 起必在每个窗口中出现,将其放入桶 d。
  3. 对桶求前缀最大值,使用独立存在标记兼容负数。

代码实现

class Solution {
    public int[] sharedMax(int[] nums) {
        int n = nums.length;
        Map<Integer, int[]> stats = new HashMap<>();

        for (int i = 0; i < n; i++) {
            int[] s = stats.computeIfAbsent(nums[i], x -> new int[] {
                -1,
                0
            });

            s[1] = Math.max(s[1], i - s[0]);
            s[0] = i;
        }

        int[] bucket = new int[n + 1];
        int[] out = new int[n];
        boolean[] has = new boolean[n + 1];

        for (Map.Entry<Integer, int[]> e : stats.entrySet()) {
            int[] s = e.getValue();
            int d = Math.max(s[1], n - s[0]);

            if (!has[d] || e.getKey() > bucket[d]) {
                bucket[d] = e.getKey();
            }

            has[d] = true;
        }

        boolean found = false;
        int best = 0;

        for (int k = 1; k <= n; k++) {
            if (has[k]) {
                if (!found || bucket[k] > best) {
                    best = bucket[k];
                }

                found = true;
            }

            out[k - 1] = found ? best : -1;
        }

        return out;
    }
}
func sharedMax(nums []int) []int {
    n := len(nums)
    stats := map[int][2]int{}
    for i, x := range nums {
        s, ok := stats[x]
        if !ok {
            s[0] = -1
        }
        s[1] = max(s[1], i-s[0])
        s[0] = i
        stats[x] = s
    }
    bucket := make([]int, n+1)
    has := make([]bool, n+1)
    for x, s := range stats {
        d := max(s[1], n-s[0])
        if !has[d] || x > bucket[d] {
            bucket[d] = x
        }
        has[d] = true
    }
    out := make([]int, n)
    found, best := false, 0
    for k := 1; k <= n; k++ {
        if has[k] {
            if !found || bucket[k] > best {
                best = bucket[k]
            }
            found = true
        }
        out[k-1] = -1
        if found {
            out[k-1] = best
        }
    }
    return out
}

复杂度分析

  • 时间复杂度:使用哈希表,期望时间 $O(n)$。
  • 空间复杂度:$O(n)$。

关键点总结

[!green]

两个出现位置之间最长缺失段长为 d-1,所以每个 k 窗口都含该值当且仅当 k>=d。

易错点总结

[!yellow]

不是求每个窗口各自的最大值,而是先取全部窗口的元素交集,再取最大值。

相似题目

题目 难度 关联与区别
828. 统计子串中的唯一字符 困难 都按相同元素的相邻出现位置界定窗口边界;本题用最大空隙推导能覆盖每个窗口的最小长度,该题用前后位置统计恰好出现一次的区间贡献。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2026/2558441635
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!