半径为k的子数组平均值

题目要求:

image

这题是经典的定长滑动窗口.

因为:

  • 半径是K
  • 以i为中新
  • 子数组长度固定为:
    2k+1

一、暴力解法

class Solution {
    public int[] getAverages(int[] nums, int k){

        int[] ans = new int[n]

        for(int i = 0;i<n;i++){
  
            //左边界
            int left = i-k;

            //右边界
            int right = i+k;

            //无法形成完整窗口
            if(left<0||right>n-1){
                ans[i] = -1;
                continue;
            }

            //计算窗口总和
            long sum = 0;

            for(int j = left;j<=right;j++){
                  sum += nums[j];
            }
  
            //求窗口的平均值
            ans[i] = (int)(sum/2*k+1);
         }

         return ans;
    }
}

二、暴力解法的时间复杂度

  • 外层循环
    for (int i = 0; i < n; i++)
    执行n
  • 内层循环
    for (int j = left; j <= right; j++)
    执行2k+1

所以总时间复杂度
O(n×(2k+1))
通常写成:
O(nk)

三、滑动窗口思想(核心)

通过观察暴力算法我们发现窗口中的部分元素会被重复计算
为了不重复计算整个窗口
我们可以通过移动窗口进行优化:
删除左边元素,加入右边元素
总结为
入->更新->出

image

以下是灵茶山艾府给出的Java版题解O(n)定长滑动窗口

class Solution {
    public int[] getAverages(int[] nums, int k) {
        int n = nums.length;
        int[] avgs = new int[n];
        Arrays.fill(avgs, -1);
        long s = 0; // 维护窗口元素和(注意类型!)
        for (int i = 0; i < n; i++) {
            // 1. 进入窗口
            s += nums[i];
            if (i < k * 2) { // 窗口大小不足 2k+1
                continue;
            }
            // 2. 记录答案
            avgs[i - k] = (int) (s / (k * 2 + 1));
            // 3. 离开窗口
            s -= nums[i - k * 2];
        }
        return avgs;
    }
}

代码中调用了Arrays工具类的fill方法数组进行初始化
Arrays.fill(avgs,-1);
假如不使用该工具类的方法,我们需要遍历整个数组来逐个赋值

for(int i = 0;i<nums.length;i++){
    avgs[i] = -1;
}

四、GPT算法流程

1. 初始化答案数组

默认全部
-1
因为有些位置无法形成完整窗口

2. 特殊情况

如果2*k+1>n
说明连一个完整窗口都没有
直接全部返回-1

3. 先计算第一个窗口和

4. 开始滑动窗口

每次:

  • 加上右边
  • 减去左边

并计算中心位置平均值

五、GPT版的Java代码实现

class Solution {
    public int[] getAverages(int[] nums, int k) {

        int n = nums.length;

        // 结果数组
        int[] ans = new int[n];

        // 默认填充 -1
        Arrays.fill(ans, -1);

        // 窗口长度
        int len = 2 * k + 1;

        // 连一个完整窗口都没有
        if (len > n) {
            return ans;
        }

        // 用 long 防止溢出
        long sum = 0;

        // 1. 先计算第一个窗口和
        for (int i = 0; i < len; i++) {
            sum += nums[i];
        }

        // 第一个窗口中心
        ans[k] = (int)(sum / len);

        // 2. 滑动窗口
        for (int right = len; right < n; right++) {

            // 新加入元素
            sum += nums[right];

            // 移除左边元素
            sum -= nums[right - len];

            // 当前窗口中心
            int center = right - k;

            ans[center] = (int)(sum / len);
        }

        return ans;
    }
}

一、第一处区别是GPT进行了特殊情况的处理

if(len>n){
  return ans;
}

而灵茶山艾府并没有做单独处理
而是直接进入到窗口的计算

 if (i < k * 2) { // 窗口大小不足 2k+1
    continue;
 }

如果没有形成窗口就直接跳过后面的代码提前进入下一个for循环。
for循环结束以后返回数组avgs
这样数组长度小于窗口长度,无法形成窗口的时候,滑动窗口求和的代码就不会被执行,
循环结束以后avgs并没有变动,返回的avgs每个值都是-1正好处理了这种特殊情况

二、第二处区别是GPT特别处理了第一个窗口

// 1. 先计算第一个窗口和
        for (int i = 0; i < len; i++) {
            sum += nums[i];
        }

 // 第一个窗口中心
        ans[k] = (int)(sum / len);

然后再进入滑动窗口的逻辑

for(int right = len;right<n;right++){
    
    //新加入元素
    sum += nums[right];

    //移除左边元素
    sum -= nums[right-len];

    //当前窗口中心
    int center = right-k;

    ans[center] = (int)(sum/len);
}

    return res;
}
posted @ 2026-05-25 21:35  ZealousMclaren  阅读(9)  评论(0)    收藏  举报