问题描述
小U手上有一个整数数组,他想知道如果从数组中删除任意一个元素后,能得到的长度为 k 的子数组和的最大值。你能帮小U计算出这个结果吗?
如果数组恰好为 k 个元素,那么不进行删除操作。
测试样例
样例1:
输入:
n = 5,k = 3,nums = [2, 1, 3, -1, 4]
输出:8
样例2:
输入:
n = 6,k = 2,nums = [-1, -1, 5, -2, 3, 4]
输出:8
样例3:
输入:
n = 4,k = 2,nums = [-5, -3, 2, 1]
输出:3
解题思路
滑动窗口 + 枚举删除
1. 滑动窗口求初始最大和:
- 遍历数组,找到所有长度为 k + 1(前提是数组长度 n != k,否则 k 不用加1) 的子数组的和。
2. 枚举删除一个元素:
- 遍历当前滑动窗口中的所有元素,找到当前滑动窗口中的最小值,删除该最小值,用滑动窗口再次求解子数组的最大和。
3. 比较取最大值。
实现思路
- 初始化变量保存当前最大和 maxSum。
- 判断 数组长度 n 是否等于 k
- 如果 n == k,不需要删除元素,输出数组所有的元素之和;
- 如果 n != k,需要将 k + 1;因为我们的前提是删除一个元素之后的子数组和的最大值,
-
- 被删除的这个元素可能是在一个子数组中间的某个元素,删除之后这个子数组长度就等于 k - 1 了,所以我们需要提前将 k + 1,如此在求得滑动窗口大小为 k + 1时,删除当前窗口的范围内的最小值之后的子数组长度还是 k。
-
- 这个被删除的元素在子数组的左右边缘时,就算 k + 1了,也不会影响最后的结果。
- 遍历所有大小为 k 的滑动窗口: 删除当前滑动窗口的最小值之后,更新最大和 maxSum。
代码实现
public static int solution(int n, int k, int[] nums) {
// 如果数组长度恰好为 k,不需要删除元素
if (n == k) {
int sum = 0;
for (int num : nums) {
sum += num;
}
return sum;
}
k++;
// 滑动窗口计算长度为 k 的最大和
int maxSum = Integer.MIN_VALUE, windowSum = 0, min = nums[0];
for (int i = 0; i < k; i++) {
windowSum += nums[i];
min = Math.min(min, nums[i]);
}
maxSum = windowSum - min;
// 枚举找到最小值
for (int i = k; i < n; i++) {
windowSum += nums[i] - nums[i - k];
min = nums[i];
for (int j = i - k + 1; j <= i; j++) {
min = Math.min(min, nums[j]);
}
maxSum = Math.max(maxSum, windowSum - min);
}
return maxSum;
}
时间复杂度
时间复杂度:
- 外层循环枚举删除每个元素:
- 内层滑动窗口求和:
空间复杂度:
学习心得
- 理解窗口的概念:滑动窗口是一个固定大小或动态大小的子数组,它沿着数组的某个方向移动。理解窗口如何移动和如何维护窗口内的状态是关键。
- 初始化窗口:在开始滑动之前,需要正确初始化窗口,包括计算窗口内元素的初始和或其他状态。
- 窗口的滑动:窗口的滑动通常涉及到两个操作:从窗口的起始位置移除一个元素,以及在窗口的末尾添加一个新元素。这两个操作需要同步进行,以保持窗口的大小不变。
- 状态更新:在窗口滑动时,需要更新窗口的状态,比如窗口内元素的和、最大值、最小值等。
- 记录结果:在滑动窗口的过程中,需要记录可能的结果,如最大和、最小和以及对应的窗口位置。
- 边界条件:处理边界条件非常重要,比如窗口的起始和结束位置,以及数组长度小于窗口大小时的情况。
知识点延伸
滑动窗口技术不仅仅局限于子数组和问题,它还可以应用于多种算法问题,以下是一些延伸知识点:
-
子串问题:滑动窗口常用于处理字符串相关的子串问题,如寻找最长无重复子串、最小覆盖子串等。
-
双指针技术:滑动窗口通常与双指针技术结合使用,其中一个指针指向窗口的起始位置,另一个指针指向窗口的结束位置。
-
动态窗口大小:在某些问题中,窗口的大小可能是动态变化的,这要求算法能够根据特定条件调整窗口的大小。
-
前缀和与哈希表:滑动窗口可以与前缀和或哈希表结合,用于快速计算窗口内元素的和或统计窗口内元素的频率。
-
性能优化:滑动窗口技术可以显著降低算法的时间复杂度,从O(nk)降低到O(n),这在处理大数据集时尤其重要。