定长滑动窗口题目的思路
特点:1.字符串的子串 2.数组的子数组
思路:入,更新,出 【来自:灵茶山艾府】
1. 定长字串中元音的最大数目
题目:1456. 定长子串中元音的最大数目
https://leetcode.cn/problems/maximum-number-of-vowels-in-a-substring-of-given-length/
给你字符串 s
和整数 k
。
请返回字符串 s
中长度为 k
的单个子字符串中可能包含的最大元音字母数。
英文中的 元音字母 为(a
, e
, i
, o
, u
)。
示例 1:
输入:s = "abciiidef", k = 3 输出:3 解释:子字符串 "iii" 包含 3 个元音字母。
思路
定长滑窗,用一个滑动窗口,先让窗口中装满元素,然后依次向右边移动,在移动过程中不断进行最大值的更新
代码
class Solution {
public int maxVowels(String s, int k) {
// 滑动窗口的思想,即窗口逐个向右滑动,把左边的元素排出 【定长滑窗】
char[] str = s.toCharArray();
int ans = 0;
int vowel = 0; // 确定元音字母数目
for(int i=0; i< str.length; i++) {
// 进入窗口中,此时添加元素入窗口
if (str[i] == 'a' || str[i] == 'e' || str[i] == 'i' || str[i] == 'o' || str[i] == 'u') {
vowel++;
}
if(i < k-1) { // 此时窗口还没有塞满就不需要出窗口
continue; // 继续加入元素
}
// 执行时说明已经窗口满了,开始滑动
// 2. 更新
ans = Math.max(ans, vowel);
// 3. 出
char out = str[i-k+1]; // 最左边的元素出去
if (out == 'a' || out == 'e' || out == 'i' || out == 'o' || out == 'u') {
vowel --; // 出去的元素是元音元素,统计的字符数目减一
}
}
return ans;
}
}
复杂度分析
时间复杂度:O(n)
空间复杂度:O(n) 将字符串转化为字符数组,使用了额外的数组空间
2. 子数组最大平均数I
给你一个由 n
个元素组成的整数数组 nums
和一个整数 k
。
请你找出平均数最大且 长度为 k
的连续子数组,并输出该最大平均数。
任何误差小于 10-5
的答案都将被视为正确答案。
示例 1:
输入:nums = [1,12,-5,-6,50,3], k = 4 输出:12.75 解释:最大平均数 (12-5-6+50)/4 = 51/4 = 12.75
代码
class Solution {
public double findMaxAverage(int[] nums, int k) {
// 滑动窗口,选择k个元素,然后从左往右依次移动,移动过程中不断的更新最大平均值
double average = -Double.MAX_VALUE; // 定义一个最小的结果
double sum = 0.0f;
for (int i = 0; i < nums.length; i++) {
// 进入窗口,即统计平均值
sum += nums[i];
if (i - k + 1 < 0) { // 窗口未满,比如i=1;k=2此时窗口刚好满了,应该进入后面
continue;
}
// 窗口满了,开始更新,并出元素
average = Math.max(average, sum);
sum = sum - nums[i - k + 1]; // 最左边元素出去
}
return average / k; // 注意这个average仅是长度为k的连续子数组最大和
}
}
// 更快的解法
class Solution {
public double findMaxAverage(int[] nums, int k) {
int sum = 0;
for(int i = 0; i < k; i ++) // 先存满窗口
sum += nums[i];
int temp = sum;
for(int i = 0, j = k; j < nums.length; i ++, j ++) {
temp -= nums[i];
temp += nums[j];
sum = Math.max(sum, temp);
}
return (double) sum / k;
}
}
两个易错点:
1. 要使用双精度类型来定义结果,使用float可能会丢失Int数据的精度
2. 刚开始要定义成最小值 double average = - Double.MAX_VALUE; 注意这个最小值并不是Double.MIN_VALUE; 因为这个只是一个非常小的正数。不是负值。
复杂度分析
3. 大小为K且平均值大于等于阈值的子数组数目
给你一个整数数组 arr
和两个整数 k
和 threshold
。
请你返回长度为 k
且平均值大于等于 threshold
的子数组数目。
示例 1:
输入:arr = [2,2,2,2,5,5,5,8], k = 3, threshold = 4 输出:3 解释:子数组 [2,5,5],[5,5,5] 和 [5,5,8] 的平均值分别为 4,5 和 6 。其他长度为 3 的子数组的平均值都小于 4 (threshold 的值)。
思路
先把窗口填满,然后逐步向右滑动,滑动的过程中左边的元素逐步出去
代码
class Solution {
public int numOfSubarrays(int[] arr, int k, int threshold) {
// 同理: step1: 入 step2: 更新 step3: 出
double sum = 0.0; // 记录和
int ans = 0; //记录结果数目
for(int i=0; i<k-1; i++) {
sum += arr[i]; // 先把窗口填满,留一个位置
}
for(int i=k-1; i<arr.length; i++) {
sum += arr[i];
if(sum/k >= threshold) { // 更新
ans ++;
}
sum = sum - arr[i-k+1]; // 左边元素出去
}
return ans;
}
}
复杂度分析
时间复杂度O(n)
4. 半径为k的子数组平均值
给你一个下标从 0 开始的数组 nums
,数组中有 n
个整数,另给你一个整数 k
。
半径为 k 的子数组平均值 是指:nums
中一个以下标 i
为 中心 且 半径 为 k
的子数组中所有元素的平均值,即下标在 i - k
和 i + k
范围(含 i - k
和 i + k
)内所有元素的平均值。如果在下标 i
前或后不足 k
个元素,那么 半径为 k 的子数组平均值 是 -1
。
构建并返回一个长度为 n
的数组 avgs
,其中 avgs[i]
是以下标 i
为中心的子数组的 半径为 k 的子数组平均值 。
x
个元素的 平均值 是 x
个元素相加之和除以 x
,此时使用截断式 整数除法 ,即需要去掉结果的小数部分。
- 例如,四个元素
2
、3
、1
和5
的平均值是(2 + 3 + 1 + 5) / 4 = 11 / 4 = 2.75
,截断后得到2
。
代码
class Solution {
public int[] getAverages(int[] nums, int k) {
// 1. 对刚开始的k个元素直接设置平均值为-1,因为它们不存在左边的元素;
// 2. 对下标大于等于 nums.length - k 的元素设置为-1,因为它们右边也没有元素;
// 3. 对于剩下的元素从第一个开始逐步使用定长的滑动窗口,窗口的长度为2K+1;
// 入,更新,出
int[] avg = new int[nums.length];
double sum = 0;
// 先把前2k的数据加起来,2k加1先不加,根据后面的信息进行处理
if(nums.length >= 2*k + 1) {
for(int i=0; i<2*k; i++) {
sum += nums[i];
}
}
for(int i=0;i<nums.length; i++) {
if(i < k || i >= nums.length - k) {
avg[i] = -1;
continue;
}
sum += nums[i+k]; //把最后一个2k+1元素加上
avg[i] = (int)Math.floor(sum/(2*k+1));
// 出
sum = sum - nums[i-k]; // 左边元素出去
}
return avg;
}
}
// 其他程序
class Solution {
public int[] getAverages(int[] nums, int k) {
int[] result = new int[nums.length];
long sum = 0;
int length = 2 * k + 1;
for (int i = 0; i < 2 * k && i < nums.length; i++) {
sum += nums[i];
}
for (int i = 0; i < nums.length; i++) {
if (i < k || i + k >= nums.length) {
result[i] = -1;
} else {
sum += nums[i + k];
result[i] = (int) (sum / length);
sum -= nums[i - k];
}
}
return result;
}
}
复杂度分析
5. 得到K个黑块的最少涂色次数
给你一个长度为 n
下标从 0 开始的字符串 blocks
,blocks[i]
要么是 'W'
要么是 'B'
,表示第 i
块的颜色。字符 'W'
和 'B'
分别表示白色和黑色。
给你一个整数 k
,表示想要 连续 黑色块的数目。
每一次操作中,你可以选择一个白色块将它 涂成 黑色块。
请你返回至少出现 一次 连续 k
个黑色块的 最少 操作次数。
示例 1:
输入:blocks = "WBBWWBBWBW", k = 7 输出:3 解释: 一种得到 7 个连续黑色块的方法是把第 0 ,3 和 4 个块涂成黑色。 得到 blocks = "BBBBBBBWBW" 。 可以证明无法用少于 3 次操作得到 7 个连续的黑块。 所以我们返回 3 。
思路:
本题相对于之前的题目要转变一下问题问的东西,题目问的是一次连续k个黑色块的最少操作次数;即在问对于字符串中的一个连续长度为k的子串中最少有多少个白块。把这些白块染成黑色了即为最少的操作次数。
class Solution {
public int minimumRecolors(String blocks, int k) {
// 要转换题目,确定是不是求在某个长度内的最小串
// 思路:需要达到K值的最小染色次数,即问的就是在K个窗口内最小“W”的次数;只要把这个“W”染成黑色即为最小的染色次数
int countW = 0;
int minW = Integer.MAX_VALUE; // minW用以记录k个长度内的 W 最小值
char[] blocksCh = blocks.toCharArray();
for(int i=0; i<k-1; i++) {
if(blocksCh[i] == 'W') {
countW ++; // 记录k个长度中W的个数
}
}
for(int i=k-1; i<blocksCh.length; i++) {
// 1. 入
if(blocksCh[i] == 'W') {
countW ++;
}
// 2. 更新
minW = Math.min(minW,countW);
// 3. 左边的字符出去
if(blocksCh[i-k+1] == 'W') { // 出去的是白的,那么countW--
countW--;
}
}
return minW;
}
}
复杂度分析:
时间复杂度O(n); 空间复杂度:O(1);
6. 拆炸弹
你有一个炸弹需要拆除,时间紧迫!你的情报员会给你一个长度为 n
的 循环 数组 code
以及一个密钥 k
。
为了获得正确的密码,你需要替换掉每一个数字。所有数字会 同时 被替换。
- 如果
k > 0
,将第i
个数字用 接下来k
个数字之和替换。 - 如果
k < 0
,将第i
个数字用 之前k
个数字之和替换。 - 如果
k == 0
,将第i
个数字用0
替换。
由于 code
是循环的, code[n-1]
下一个元素是 code[0]
,且 code[0]
前一个元素是 code[n-1]
。
给你 循环 数组 code
和整数密钥 k
,请你返回解密后的结果来拆除炸弹!
示例 1:
输入:code = [5,7,1,4], k = 3 输出:[12,10,16,13] 解释:每个数字都被接下来 3 个数字之和替换。解密后的密码为 [7+1+4, 1+4+5, 4+5+7, 5+7+1]。注意到数组是循环连接的。
参考题解:通过确定起始位置可以把 k<0 与 k>0 的情况结合起来。
class Solution {
public int[] decrypt(int[] code, int k) {
// 根据题意:我需要的就是接下来的k个数字之和与之前的k个数字之和
// 对于接下来的k个数字
// 接下来的k个数字即在k个窗口的基础上,每次加入最右边的元素,如果超过了数组长度就对数组长度取余即可;然后删去最左边的元素
// 对于之前的k个数字
// 也是维护一个滑动窗口,在k个窗口的基础上去掉最左边的元素,然后加上当前元素的前一个元素;
// 本题思路:即维护两个滑动窗口
if(k == 0) {
for(int i=0 ; i<code.length; i++) {
code[i] = 0;
}
return code;
}
int[] decode = new int[code.length]; // 记录更新后的解密密码;因为如果访问一个就更新一个code[i]的值的话,后面的元素使用这个元素就会出问题
// 维护后面的滑动窗口
if(k > 0) {
int sum = 0; // sum为接下来的元素之和
for(int i=1; i<k; i++) {
sum += code[i];
}
for(int i=0; i<code.length; i++) {
// 1. 加入最后一个元素
sum += code[(i+k)%code.length];
// 2. 更新
decode[i] = sum;
// 3. 删去最左边的元素,即当前元素的下一个元素
sum -= code[(i+1)%code.length];
}
}
// 维护前面的滑动窗口
if(k < 0) {
k = Math.abs(k); // 对k取反
int sum = 0; // sum为接下来的元素之和
for(int i=code.length-2; i>code.length-k-1; i--) { // 开始的滑动窗口值应为最后的k-1个元素
sum += code[i];
}
for(int i=0; i<code.length; i++) {
// 1. 加入上一个元素
if(i==0) { // 如果为第一个元素,则加入最后一个元素
sum += code[code.length-1];
}else {
sum += code[i-1];
}
// 2. 更新
decode[i] = sum;
// 3. 删去最左边的元素
// if(i >= k) {
// sum -= code[i-k]; // 说明窗口最左边元素就在元素的左边
// }else {
// sum -= code[code.length - k + i]; // 说明窗口最左边元素就在数组末尾位置
// }
// 上面的if可以合并处理
sum -= code[(code.length-k+i)%code.length];
}
}
return decode;
}
}
复杂度分析:
时间复杂度:O(n); 空间复杂度:O(n);
7. 爱生气的书店老板
有一个书店老板,他的书店开了 n
分钟。每分钟都有一些顾客进入这家商店。给定一个长度为 n
的整数数组 customers
,其中 customers[i]
是在第 i
分钟开始时进入商店的顾客数量,所有这些顾客在第 i
分钟结束后离开。
在某些分钟内,书店老板会生气。 如果书店老板在第 i
分钟生气,那么 grumpy[i] = 1
,否则 grumpy[i] = 0
。
当书店老板生气时,那一分钟的顾客就会不满意,若老板不生气则顾客是满意的。
书店老板知道一个秘密技巧,能抑制自己的情绪,可以让自己连续 minutes
分钟不生气,但却只能使用一次。
请你返回 这一天营业下来,最多有多少客户能够感到满意 。
思路+代码
class Solution {
public int maxSatisfied(int[] customers, int[] grumpy, int minutes) {
// 首先如果没有不生气的技巧,那每天不满意的人数和满意的人数都是确定的;
// 满意的人数 = 总人数 - 不满意的人数
// 可以转化为求这一天中不满意的人数
// 以minute作为滑动窗口的长度;求这个窗口内不满意人数的最大值;然后使用秘密技巧,将这些人都变成满意
int dissatisfactionNum = 0; // 记录不满意的人数
int maxNum = 0; // 记录在minute中最多生气的人数
for(int i=0; i<minutes-1; i++) {
if(grumpy[i] == 1) { // angry
dissatisfactionNum += customers[i];
}
}
for(int i=minutes-1; i<customers.length; i++) {
// 1. 入
if(grumpy[i] == 1) {
dissatisfactionNum += customers[i];
}
// 2. 更新最大值
maxNum = Math.max(maxNum,dissatisfactionNum);
// 3. 出
if(grumpy[i-minutes+1] == 1) {
dissatisfactionNum -= customers[i-minutes+1];
}
}
// 循环结束后maxNum就是minutes内生气的最大人数
int satTotalNum = 0; // 记录未使用技巧时满意总人数
// 下面这个循环的操作内容可以放到上面两个循环中进行
for(int i=0; i<customers.length; i++) {
if(grumpy[i] == 0) {
satTotalNum += customers[i];
}
}
return satTotalNum + maxNum; // 最后的满意人数即刚开始的满意人数+使用技巧后变来的满意人数
}
}
复杂度分析
时间复杂度:O(n) 空间复杂度:O(1)
8. 几乎唯一子数组的最大和
给你一个整数数组 nums
和两个正整数 m
和 k
。
请你返回 nums
中长度为 k
的 几乎唯一 子数组的 最大和 ,如果不存在几乎唯一子数组,请你返回 0
。
如果 nums
的一个子数组有至少 m
个互不相同的元素,我们称它是 几乎唯一 子数组。
子数组指的是一个数组中一段连续 非空 的元素序列。
难点:
如何判断在当前k长度的窗口中的元素的不同种类数目需要大于,在不超时的情况下。
class Solution {
public long maxSum(List<Integer> nums, int m, int k) {
// 使用 “var” 可以让编译器根据变量的初始化表达式来推断变量的类型
/*
*nums.stream()是 Java 8 中引入的Stream API的用法。
*在这里,nums是一个List<Integer>类型的集合。stream()方法用于从这个集合创建一个Stream(流)对加
*简洁和高效的方式处理集合中的数据。
*在这段代码中,后续通过.mapToInt(i -> i).toArray();对这个流进行进一步操作,将列表中的整数转换为*基本类型的整数数组。
*象。
*Stream提供了一种对集合数据进行函数式操作的方式,可以进行一系列的操作,如过滤、映射、归约等,以更
*/
// 将输入的List<Integer>类型的nums转换为基本类型的整数数组a,以便后续操作更高效。
var a = nums.stream().mapToInt(i -> i).toArray();
// ans用于存储最终的最大子数组和。sum用于临时存储当前正在考虑的子数组的元素总和。
long ans = 0, sum = 0;
// 创建一个HashMap,用于记录当前子数组中每个元素出现的次数。
var cnt = new HashMap<Integer, Integer>();
for (int i = 0; i < k - 1; i++) { // 先统计 k-1 个数
sum += a[i];
cnt.merge(a[i], 1, Integer::sum); // cnt[a[i]]++ // 元素出现次数+1
}
for (int i = k - 1; i < nums.size(); i++) {
sum += a[i]; // 再添加一个数就是 k 个数了
cnt.merge(a[i], 1, Integer::sum); // cnt[a[i]]++
// 如果当前子数组中不同元素的数量至少为m,则更新ans为当前子数组和与ans中的较大值。
if (cnt.size() >= m)
ans = Math.max(ans, sum);
// 从子数组和中减去移出的元素。
int out = a[i - k + 1];
sum -= out; // 下一个子数组不包含 out,移出窗口
// 更新移出元素在cnt中的出现次数,如果出现次数变为 0,则从cnt中移除该元素。
if (cnt.merge(out, -1, Integer::sum) == 0) // --cnt[out] == 0
cnt.remove(out);
}
return ans;
}
}
*在这段代码中,cnt.merge(a[i], 1, Integer::sum)和cnt.merge(out, -1, Integer::sum)使用了 *Java 8 中Map接口的merge方法。
*merge方法的作用是:如果指定的键已经存在于Map中,则将给定的值与键对应的现有值进行合并操作;如果键*不存在,则将键和给定的值插入到Map中。
*具体解释如下:
*cnt.merge(a[i], 1, Integer::sum):
*a[i]是要合并的键,也就是当前遍历到的数组元素。
*1是如果键不存在时要插入的值。
*Integer::sum是一个BinaryOperator函数式接口的实现,它指定了如果键已经存在时,如何合并新值和旧
*值。在这里,它将新值1和旧值相加,也就是实现了对键对应的计数器进行递增的操作。
*cnt.merge(out, -1, Integer::sum):
*out是要合并的键,也就是即将从窗口移出的元素。
*-1是如果键不存在时要插入的值(这里实际上键一定存在,因为是从窗口移出的元素,之前一定在cnt中出现
*过)。
*Integer::sum同样将新值-1和旧值相加,实现了对键对应的计数器进行递减的操作。如果递减后计数器变为 *0,则从Map中移除该键。
// 作者:灵茶山艾府
// 链接:https://leetcode.cn/problems/maximum-sum-of-almost-unique-subarray/solutions/2424847/hua-dong-chuang-kou-fu-ti-dan-pythonjava-2vd6/
// 来源:力扣(LeetCode)
// 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
// 以下代码可以使用但是超时了
// class Solution {
// public long maxSum(List<Integer> nums, int m, int k) {
// // 滑动窗口:
// // 从左往右维护长度为k的滑动窗口
// // 判断k数组中的重复元素数目; // 可以把这个k数组放到Map中,用Map记录元素出现的次数
// // 维护两个东西:一个长度为k的滑动窗口;一个Map维护出现的次数
// HashMap<Integer,Integer> map = new HashMap<Integer,Integer>();
// long max = 0; // 记录最大值
// long sum = 0; // 记录当前访问的子数组的和,但这个子数组不一定满足几乎唯一的条件,只要几乎唯一才可以更新max
// // 先进入k-1个元素
// int cur = 0; // 记录此时从List中取的元素,只是一个中间量
// int curVal = 0; // 记录取出的当前元素的val值
// for(int i=0; i<k-1; i++) {
// cur = nums.get(i);
// sum += cur;
// if(!map.containsKey(cur)) {
// map.put(cur,1); // 表示出现1次
// }else { // 已经包含就次数加1
// curVal = map.get(cur);
// map.replace(cur,curVal+1);
// }
// }
// int count = 0; // 记录当前窗口中互不相同的元素个数
// // 开始滑动
// for(int i=k-1; i<nums.size(); i++) {
// // 1. 入
// cur = nums.get(i);
// sum += cur;
// if(!map.containsKey(cur)) {
// map.put(cur,1); // 表示出现1次
// }else { // 已经包含就次数加1
// curVal = map.get(cur);
// map.replace(cur,curVal+1);
// }
// count = 0;
// // 判断Map中value不为0的元素key的个数即可
// for (Integer j : map.keySet()) {
// if(map.get(j) > 0) {
// count ++;
// }
// }
// if(count >= m) { // 为几乎唯一子数组
// // 2. 更新max
// max = Math.max(max, sum);
// }
// // 3. 出
// // step1: sum删除最左边的元素值
// // step2: 更新map,使得map中出去元素的key对应的value值 -1;
// sum -= nums.get(i-k+1);
// int n = map.get(nums.get(i-k+1));
// map.replace(nums.get(i-k+1),n-1);
// }
// return max;
// }
// }
9. 长度为K子数组中的最大和
给你一个整数数组 nums
和一个整数 k
。请你从 nums
中满足下述条件的全部子数组中找出最大子数组和:
- 子数组的长度是
k
,且 - 子数组中的所有元素 各不相同 。
返回满足题面要求的最大子数组和。如果不存在子数组满足这些条件,返回 0
。
子数组 是数组中一段连续非空的元素序列。
思路
本题思路于第8题相同,只需要将m修改成 k 即可
class Solution {
public long maximumSubarraySum(int[] nums, int k) {
// 同理维护一个长度为k的滑动窗口
// 使用Map来记录元素出现的次数
long ans = 0; // 用于记录最后的结果值
long sum = 0; // 记录当前窗口的和
var countMap = new HashMap<Integer,Integer>();
// 前k-1个元素入队
for(int i=0; i<k-1; i++) {
sum += nums[i];
countMap.merge(nums[i],1,Integer::sum); // 看给定的键存不存在,不存在则赋值为1,存在则新值+旧值
}
// 开始滑动
for(int i=k-1; i<nums.length; i++) {
// 1. 入
sum += nums[i];
countMap.merge(nums[i],1,Integer::sum);
// 2. 更新
// 2.1 判断当前窗口中元素是否各不相同
if(countMap.size() == k) {
// 不相同则满足要求:更新ans值
ans = Math.max(ans,sum);
}
// 3. 出
int out = nums[i-k+1];
sum -= out;
// 将map中key为out的元素的value值减1;并返回处理后的值;// 处理后为0则删除这个键
if(countMap.merge(out,-1,Integer::sum) == 0) {
countMap.remove(out);
}
}
return ans;
}
}
10. 可获得的最大点数 【逆向思想】
几张卡牌 排成一行,每张卡牌都有一个对应的点数。点数由整数数组 cardPoints
给出。
每次行动,你可以从行的开头或者末尾拿一张卡牌,最终你必须正好拿 k
张卡牌。
你的点数就是你拿到手中的所有卡牌的点数之和。
给你一个整数数组 cardPoints
和整数 k
,请你返回可以获得的最大点数。
思路
以下分析了三种思路:其中贪心算法的思想是存在问题,只能保证当前的最优,但最后的结果不一定是最优。
class Solution {
public int maxScore(int[] cardPoints, int k) {
// (×)贪心思路:每次都拿最大的,如果两张一样大,就看两张的下一张,拿去下一张更大的
// 注意:贪心的思路是有问题的:局部最优不一定得到结果最优
// 比如 5 2 7 3 4 3 ,要取4个数,按照贪心的思路就会取 5 3 4 3; 最后 sum = 15
// 但如果取的是 5 2 7 3 最后sum = 17,显然点数更大
// 思路1:滑动窗口 逆向思维
// 因为要从左右的点数最大,即留下来的点数之和会最小,而且这些点数都是连续的;
// 所以相当于是在找数组张 长度为 n-k 的连续子数组和的最小值;维护一个n-k长度的滑动窗口即可
// n-k个元素先进入窗口
// int n = cardPoints.length;
// int min = 0; // 记录每个窗口内元素的最小值
// for(int i=0 ; i< n-k; i++) {
// min += cardPoints[i];
// }
// int ans = min;
// // 开始滑动
// for(int i=n-k; i<n; i++) {
// // 1. 入
// min += cardPoints[i];
// // 3. 出 // 注意这里出要放在更新的前面,因为入之后的窗口元素个数为
// // n-k+1; 需要先把最左边的元素先出去才可以进行更新结果
// // 如果依旧按照之间使用 n-k-1 元素先入窗口,可能会出现数组越界的问题,起始下标变成了-1
// min -= cardPoints[i-(n-k)];
// // 2. 更新
// ans = Math.min(ans, min);
// }
// int sum = 0; // 记录cardPoints 数组的和
// for(int i=0; i< n ; i++) {
// sum += cardPoints[i];
// }
// return sum - ans;
// 思路2:正向思维
// 最后的结果的情况为前k个数; 前k-1个数与最后一个数; 前k-2个数与后2个数... 前1个数与后k-1个数,后k个数
// 只要不断维护前缀和,然后更新最大值即可
int prefixSum = 0;
for(int i=0; i<k; i++) { // 收集前k个元素的前缀和
prefixSum += cardPoints[i];
}
int ans = prefixSum;
// 不断更新最小值; 不断加入最后一个元素,删除当前前缀和的最后一个元素
// 这个过程总共进行k次
for(int i=1; i<=k ; i++) {
prefixSum += cardPoints[cardPoints.length - i] - cardPoints[k - i];
ans = Math.max(ans,prefixSum);
}
return ans;
}
}
11.