我理解的算法 - 1423.可获得的最大点数(前后缀和+滑动窗口)

我理解的算法 - 1423.可获得的最大点数(前后缀和+滑动窗口)

题目

几张卡牌 排成一行,每张卡牌都有一个对应的点数。点数由整数数组 cardPoints 给出。

每次行动,你可以从行的开头或者末尾拿一张卡牌,最终你必须正好拿 k 张卡牌。

你的点数就是你拿到手中的所有卡牌的点数之和。

给你一个整数数组 cardPoints 和整数 k,请你返回可以获得的最大点数。

示例 1:
输入:cardPoints = [1,2,3,4,5,6,1], k = 3
输出:12
解释:第一次行动,不管拿哪张牌,你的点数总是 1 。但是,先拿最右边的卡牌将会最大化你的可获得点数。最优策略是拿右边的三张牌,最终点数为 1 + 6 + 5 = 12 。

示例 2:
输入:cardPoints = [2,2,2], k = 2
输出:4
解释:无论你拿起哪两张卡牌,可获得的点数总是 4 。

示例 3:
输入:cardPoints = [9,7,7,9,7,7,9], k = 7
输出:55
解释:你必须拿起所有卡牌,可以获得的点数为所有卡牌的点数之和。

示例 4:
输入:cardPoints = [1,1000,1], k = 1
输出:1
解释:你无法拿到中间那张卡牌,所以可以获得的最大点数为 1 。

示例 5:
输入:cardPoints = [1,79,80,1,1,1,200,1], k = 3
输出:202

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/maximum-points-you-can-obtain-from-cards


前缀和+后缀和

这道题目我们也可以使用前缀和+后缀和的思路去解,我们可以从题目中看到规律,

  1. k k k的个数可以拆分成是从数组前往后取连续的 0 − m 0-m 0m个数加上从数组最后往前取连续的 0 − n 0-n 0n个数,并且 m + n = k m+n=k m+n=k,从示例[1,2,3,4,5,6,1], k = 3来看,即我们可以前面先取0个后面取3个然后前面取1个后面取2个以此类推来计算
  2. 每次 k k k个数的和为从数组前往后取连续的 0 − m 0-m 0m个数加上从数组后往前取连续的 0 − n 0-n 0n个数所有数的总和
  3. 取最大的和即为最终结果

综合上面的规律,我们很容易可以写出来这种思路的代码

public int maxScore(int[] cardPoints, int k) {
    int[] prefixSums = new int[cardPoints.length];
    int[] suffixSums = new int[cardPoints.length];
    //前缀和
    int preSum = 0;
    for(int i = 0; i < cardPoints.length; i++){
        preSum += cardPoints[i];
        prefixSums[i] = preSum;
    }

    //后缀和
    int sufSum = 0;
    for(int j = cardPoints.length - 1; j >= 0; j--){
        sufSum += cardPoints[j];
        suffixSums[j] = sufSum;
    }

    int maxSum = 0;
    for(int m = 0; m <= k; m++){
        int prefixSum = m == 0 ? 0 : prefixSums[m - 1];


        int n = k - m;
        int suffixSum = n == 0 ? 0 : suffixSums[cardPoints.length - n];
        int sum = prefixSum + suffixSum;
        maxSum = Math.max(maxSum, sum);
    }
    return maxSum;
}

不断优化

上面代码要注意的是,由于我们计算前缀和的时候,第0个位置的前缀和其实是拿1个数的时候的和,所以我们需要在计算最终和的时候需要在前缀和数组中取当前m值减1下标位置的值,所以需要做一下取0个数的判断,即取0个数的和即为0,不然会数组下标越界啦

另外,在计算前缀和的时候可以放入最终的计算和的遍历一起,这样也得到了一定优化,可以优化成如下代码

public int maxScore(int[] cardPoints, int k) {
    int[] suffixSums = new int[cardPoints.length];
    //后缀和
    int sufSum = 0;
    for(int j = cardPoints.length - 1; j >= 0; j--){
        sufSum += cardPoints[j];
        suffixSums[j] = sufSum;
    }

    //前缀和与最终计算一起进行
    int maxSum = 0;
    int prefixSum = 0;
    for(int m = 0; m <= k; m++){
        prefixSum += m == 0 ? 0 : cardPoints[m - 1];

        int n = k - m;
        int suffixSum = n == 0 ? 0 : suffixSums[cardPoints.length - n];
        int sum = prefixSum + suffixSum;
        maxSum = Math.max(maxSum, sum);
    }
    return maxSum;
}

经过上面的优化后,我们又发现,其实我们的后缀和在计算的时候计算了很多不必要的数据,因为如果k的个数远远小于数组的个数,我们根本就不需要计算出所有数据的后缀和,只需要计算出k个就行了,所以我们有了另一种思路,我们只要将在k范围内的 0 − m 0-m 0m的所有情况的每个和全部计算出来后记录下来,再把在k范围内的 0 − n 0-n 0n的所有情况的和也计算并记录,将m和n组合来获取最大和的组合即可,代码如下

public int maxScore(int[] cardPoints, int k) {
    int[] nSums = new int[k + 1];//多了0个的情况,所以要+1个
    int tempSum = 0;
    for(int n = 0; n <= k; n++){
        tempSum += n == 0 ? 0 : cardPoints[cardPoints.length - n];
        nSums[n] = tempSum;
    }

    int ans = 0;
    tempSum = 0;
    for(int m = 0; m <= k; m++){
        tempSum += m == 0 ? 0 : cardPoints[m - 1];

        int sum = tempSum + nSums[k - m];
        ans = Math.max(ans, sum);
    }
    return ans;
}

滑动窗口

感觉挺好了哈,但是这道题目其实还不是最优解,最优解其实可以用滑动窗口来做,其实我们上面的解法已经有了点这个意思了,我们上面的思路都是顺着题目来的,分别取两头的数据和来做的,其实我们逆向思维一下,很快也能想出来,题目要求的其实是数组两端的一个和,那么我们这次计算中间的连续段的和,然后用数组的总和减去中间连续段的和既可以得出来两端连续数的总和了,我们取最小的一段总和,那么最终答案就是用总和减去最小的这段总和,这样我们只要维护一个中间的连续段的窗口,每次向右移动窗口,即可,是不是思路很清奇,我们直接写代码

public int maxScore(int[] cardPoints, int k) {
    int windowSize = cardPoints.length - k;
    int totalPoint = 0;
    for(int cardPoint : cardPoints) totalPoint += cardPoint;

    int windowPoint = 0;
    for(int i = 0; i < windowSize; i++){
        windowPoint += cardPoints[i];
    }

    int ans = windowPoint;
    for(int j = windowSize; j < cardPoints.length; j++){
        windowPoint += cardPoints[j] - cardPoints[j - windowSize];
        ans = Math.min(ans, windowPoint);

    }
    return totalPoint - ans;
}

经过这些步骤,我们从最明显的做法,一步一步优化,一步一步的分析,最终以最优化的方式做出来了这道题目,所以我们做题目都要一步一步来,不能一口吃个胖子,这道题目是很好的前后缀和以及滑动窗口的练习题目,所以这样的算法,你理解了吗?

  • 11
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 12
    评论
算法图解-python.pdf》是一本介绍算法和使用Python语言实现算法的书籍。该书的目的是帮助读者了解算法的基本概念和原理,并通过Python编程实践来加深理解。 这本书以图解的方式呈现算法的思想和应用,使复杂的算法问题变得直观易懂。读者可以通过阅读该书,学习到各种常见的算法设计和解决思路,如排序算法、搜索算法、图算法等。同时,该书也会介绍一些高级算法,如动态规划、贪婪算法等,以及如何通过Python语言实现这些算法。 《算法图解-python.pdf》的内容结构清晰,通俗易懂。每个算法都有详细的解释和示例代码,读者可以通过实际编程来加深对算法理解。此外,书中还包含了一些挑战性的练习题,供读者进一步巩固所学的知识。 通过阅读《算法图解-python.pdf》,读者不仅可以学习到算法的基本知识,还可以了解到如何运用Python语言实现这些算法。这对于刚开始学习算法和Python编程的读者来说是非常有帮助的。无论是计算机科学专业的学生,还是对算法感兴趣的爱好者,都可以从这本书中受益匪浅。 总之,《算法图解-python.pdf》是一本很好的算法入门书籍,以图解和Python编程为特色,适合各类读者学习和参考。通过阅读和实践,读者可以提高算法设计和编程实现的能力,为解决实际问题提供有效的思路和方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卡卡爾

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值