定长滑动窗口刷题记录

定长滑动窗口题目的思路

特点:1.字符串的子串  2.数组的子数组

思路:入,更新,出        【来自:灵茶山艾府】

1. 定长字串中元音的最大数目

题目:1456. 定长子串中元音的最大数目icon-default.png?t=O83Ahttps://leetcode.cn/problems/maximum-number-of-vowels-in-a-substring-of-given-length/

给你字符串 s 和整数 k 。

请返回字符串 s 中长度为 k 的单个子字符串中可能包含的最大元音字母数。

英文中的 元音字母 为(aeiou)。

示例 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

题目 :643. 子数组最大平均数 Iicon-default.png?t=O83Ahttps://leetcode.cn/problems/maximum-average-subarray-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且平均值大于等于阈值的子数组数目

1343. 大小为 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的子数组平均值

2090. 半径为 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 ,此时使用截断式 整数除法 ,即需要去掉结果的小数部分。

  • 例如,四个元素 231 和 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个黑块的最少涂色次数

2379. 得到 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. 拆炸弹

1652. 拆炸弹

你有一个炸弹需要拆除,时间紧迫!你的情报员会给你一个长度为 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 的情况结合起来。

. - 力扣(LeetCode). - 备战技术面试?力扣提供海量技术面试资源,帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。icon-default.png?t=O83Ahttps://leetcode.cn/problems/defuse-the-bomb/solutions/2765762/on-ding-chang-hua-dong-chuang-kou-python-y2py

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. 爱生气的书店老板

1052. 爱生气的书店老板

有一个书店老板,他的书店开了 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. 几乎唯一子数组的最大和

2841. 几乎唯一子数组的最大和

给你一个整数数组 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子数组中的最大和

2461. 长度为 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. 可获得的最大点数 【逆向思想】

1423. 可获得的最大点数

几张卡牌 排成一行,每张卡牌都有一个对应的点数。点数由整数数组 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. 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值