【LeetCode】1423. 可获得的最大点数 Maximum Points You Can Obtain from Cards (Python)
- 作者: 负雪明烛
- id: fuxuemingzhu
- 公众号:每日算法题
- 本文关键词:LeetCode,力扣,算法,算法题,滑动窗口,递归,前缀和,preSum,刷题群
题目地址:https://leetcode-cn.com/problems/maximum-points-you-can-obtain-from-cards/
题目描述
几张卡牌 排成一行,每张卡牌都有一个对应的点数。点数由整数数组 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
提示:
1 <= cardPoints.length <= 10^51 <= cardPoints[i] <= 10^41 <= k <= cardPoints.length
解题思路
递归
你是不是跟我一样,拿到今天题目的第一想法是模拟题目取卡牌的过程呢?模拟的方法可以用递归。但是递归的过程是把所有的可能组合方式都求了一遍,时间复杂度会达到 O(N*k) ,在题目所给出的 10 ^ 5 的数据规模下,会超时。
下面的代码是我用的递归+记忆化的方式写的,虽然有记忆化,但是因为没有降低时间复杂度,所以仍然超时。提供在这里仅供大家参考。欢迎大家提供能 AC 的递归方法。
我定义的递归函数 dfs(cardPoints, i, j, k) ,表示在 cardPoints 的第 i ~ j 的位置中(包含i,j),从两端抽取 k 个卡牌能够获得的最大点数。那么当 k == 0 的时候,说明不抽牌,结果是 0。当 k != 0 的时候,抽取 k 个卡牌能拿到的点数等于 max(抽取最左边卡牌的点数 + 剩余卡牌继续抽获得的最大点数, 抽取最右边卡牌的点数 + 剩余卡牌继续抽获得最大点数)。
时间复杂度是 O(N ^ 2) 。
class Solution(object):
def maxScore(self, cardPoints, k):
"""
:type cardPoints: List[int]
:type k: int
:rtype: int
"""
N = len(cardPoints)
self.memo = {}
return self.dfs(cardPoints, 0, N - 1, k)
def dfs(self, cardPoints, i, j, k):
if k == 0:
return 0
if (i, j) in self.memo:
return self.memo[(i, j)]
removeLeft = cardPoints[i] + self.dfs(cardPoints, i + 1, j, k - 1)
removeRight = cardPoints[j] + self.dfs(cardPoints, i, j - 1, k - 1)
res = max(removeLeft, removeRight)
self.memo[(i, j)] = res
return res
preSum
当数据规模到达了 10 ^ 5 ,已经在提醒我们这个题应该使用 O(N) 的解法。
把今天的这个问题思路整理一下,题目等价于:求从 cardPoints 最左边抽 i 个数字,从 cardPoints 最右边抽取 k - i 个数字,能抽取获得的最大点数是多少。
一旦这么想,立马柳暗花明:抽走的卡牌点数之和 = cardPoints 所有元素之和 - 剩余的中间部分元素之和。
我们同样使用模拟法,但是比递归方法高妙的地方在,我们一次性从左边抽走 i 个数字: i 从 0 到 k 的遍历,表示从左边抽取了的元素数,那么从右边抽取的元素数是 k - i 个。现在问题是怎么快速求 剩余的中间部分元素之和?
没错,preSum!我的题解中已经多次分享过 preSum 的思想,下面是 preSum 的介绍,已经看过我前几天题解的朋友可以直接跳过。
求区间的和可以用 preSum 。 preSum 方法还能快速计算指定区间段 i ~ j 的元素之和。它的计算方法是从左向右遍历数组,当遍历到数组的 i 位置时, preSum 表示 i 位置左边的元素之和。
假设数组长度为 N ,我们定义一个长度为 N+1 的 preSum 数组, preSum[i] 表示该元素左边所有元素之和(不包含当前元素)。然后遍历一次数组,累加区间 [0, i) 范围内的元素,可以得到 preSum 数组。代码如下:
N = len(nums)
preSum = range(N + 1)
for i in range(N):
preSum[i + 1] = preSum[i] + nums[i]
print(preSum)
利用 preSum 数组,可以在 O(1) 的时间内快速求出 nums 任意区间 [i, j] (两端都包含) 的各元素之和。
sum(i, j) = preSum[i + 1] - preSum[j]
综合以上的思路,我们的想法可以先求 preSum ,然后使用一个 0 ~ k 的遍历表示从左边拿走的元素数,然后根据窗口大小 windowSize = N - k ,利用 preSum 快速求窗口内元素之和。

对应的 Python 代码如下。
class Solution(object):
def maxScore(self, cardPoints, k):
"""
:type cardPoints: List[int]
:type k: int
:rtype: int
"""
N = len(cardPoints)
preSum = [0] * (N + 1)
for i in range(N):
preSum[i + 1] = preSum[i] + cardPoints[i]
res = float("inf")
windowSize = N - k
for i in range(k + 1):
res = min(res, preSum[windowSize + i] - preSum[i])
return preSum[N] - res
滑动窗口
在上面的 preSum 中,我们已经想到了,抽走的卡牌点数之和 = cardPoints 所有元素之和 - 剩余的中间部分元素之和。在 preSum 的代码里,我们是模拟了从左边拿走 i 个卡牌的过程。事实上,我们也可以直接求剩余的中间部分元素之和的最小值。只要剩余的卡牌点数之和最小,那么抽走的卡牌点数之和就最大!
求一个固定大小的窗口中所有元素之和的最小值——这是一个滑动窗口问题!与这个问题非常类似的就是前几天的每日一题 643. 子数组最大平均数 I。巧了,当时我的题解也是 preSum 和 滑动窗口 两种做法!
把剩余的中间部分元素抽象成长度固定为 windowSize = N - k 的滑动窗口。当每次窗口右移的时候,需要把右边的新位置 加到 窗口中的 和 中,把左边被移除的位置从窗口的 和 中 减掉。这样窗口里面所有元素的 和 是准确的,我们求出最大的和,最终除以 k 得到最大平均数。
这个方法只用遍历一次数组。
需要注意的是,需要根据 i 的位置,计算滑动窗口是否开始、是否要移除最左边元素:
- 当
i >= windowSize时,为了固定窗口的元素是k个,每次移动时需要将i - windowSize位置的元素移除。 - 当
i >= windowSize - 1时,滑动窗口内的元素刚好是k个,开始计算滑动窗口的最小和。
最后,用 cardPoints 的所有元素之和,减去滑动窗口内的最小元素和,就是拿走的卡牌的最大点数。
使用 Python2 写的代码如下。
class Solution(object):
def maxScore(self, cardPoints, k):
"""
:type cardPoints: List[int]
:type k: int
:rtype: int
"""
N = len(cardPoints)
windowSize = N - k # 窗口的大小
sums = 0
res = float("inf") # 正无穷大
for i in range(N):
sums += cardPoints[i]
if i >= windowSize:
sums -= cardPoints[i - windowSize]
if i >= windowSize - 1:
res = min(res, sums)
return sum(cardPoints) - res
刷题心得
- 今天这个题目,难点还是需要把抽取卡牌的问题转变为一个滑动窗口的问题。
- 问题抽象之后,preSum 和 滑动窗口 两种解法就已经呼之欲出了。
- 每日一题的作用再次凸显,同一类题目都是换汤不换药,学会思路和套路之后,就很快秒杀!
欢迎加入组织
算法每日一题是个互相帮助、互相监督的力扣打卡网站,其地址是 https://www.ojeveryday.com/
想加入千人刷题群的朋友,可以复制上面的链接到浏览器,然后在左侧点击“加入组织”,提交力扣个人主页,即可进入刷题群。期待你早日加入。
欢迎关注我的公众号:每日算法题

日期
2021 年 2 月 6 日 —— 今天是年前的最后一个假期,没剪上头发
【LeetCode】1423. 可获得的最大点数 Maximum Points You Can Obtain from Cards (Python)的更多相关文章
- Odoo8查询产品时提示"maximum recursion depth exceeded while calling a Python object"
今天在生产系统中查询产品时,莫名提示错误:maximum recursion depth exceeded while calling a Python object,根据错误日志提示,发现在查询产品 ...
- scrapy RuntimeError: maximum recursion depth exceeded while calling a Python object 超出python最大递归数异常
2019-10-21 19:01:00 [scrapy.core.engine] INFO: Spider opened2019-10-21 19:01:00 [scrapy.extensions.l ...
- 【LeetCode】117. Populating Next Right Pointers in Each Node II 解题报告(Python)
[LeetCode]117. Populating Next Right Pointers in Each Node II 解题报告(Python) 标签: LeetCode 题目地址:https:/ ...
- LeetCode 643. 子数组最大平均数 I(Maximum Average Subarray I)
643. 子数组最大平均数 I 643. Maximum Average Subarray I 题目描述 给定 n 个整数,找出平均数最大且长度为 k 的连续子数组,并输出该最大平均数. LeetCo ...
- LeetCode 414. 第三大的数(Third Maximum Number) 3
414. 第三大的数 414. Third Maximum Number 题目描述 给定一个非空数组,返回此数组中第三大的数.如果不存在,则返回数组中最大的数.要求算法时间复杂度必须是 O(n). 每 ...
- LeetCode Javascript实现 258. Add Digits 104. Maximum Depth of Binary Tree 226. Invert Binary Tree
258. Add Digits Digit root 数根问题 /** * @param {number} num * @return {number} */ var addDigits = func ...
- LeetCode 795. Number of Subarrays with Bounded Maximum
问题链接 LeetCode 795 题目解析 给定一个数组A,左右范围L.R.求子数组的数量,要求:子数组最大值在L.R之间. 解题思路 子数组必须连续,利用最大值R对数组进行分段,设定变量 left ...
- Leetcode 149.直线上最多的点数
直线上最多的点数 给定一个二维平面,平面上有 n 个点,求最多有多少个点在同一条直线上. 示例 1: 输入: [[1,1],[2,2],[3,3]] 输出: 3 解释: ^ | | o ...
- LeetCode 1255 得分最高的单词集合 Maximum Score Words Formed by Letters
地址 https://leetcode-cn.com/problems/maximum-score-words-formed-by-letters/ 题目描述你将会得到一份单词表 words,一个字母 ...
随机推荐
- dart系列之:数学什么的就是小意思,看我dart如何玩转它
目录 简介 dart:math包的构成 math Random 总结 简介 dart也可以进行数学运算,dart为数学爱好者专门创建了一个dart:math包来处理数学方面的各种操作.dart:mat ...
- 66-Reorder List
Reorder List My Submissions QuestionEditorial Solution Total Accepted: 64392 Total Submissions: 2818 ...
- KEPServeEX 6与KepOPC中间件测试
KEPServeEX 6可以组态服务器端和客户端连接很多PLC以及具有OPC服务器的设备,以下使用KEPServeEX 6建立一个OPC UA服务器,然后使用KepOPC建立客户端来连接服务器做测试. ...
- C++中的排序
下面网站解释比较好 http://www.cnblogs.com/heyonggang/archive/2013/11/03/3404371.html 1. qsort(C中的函数加上stdlib.h ...
- mysql数据查询语言DQL
DB(database)数据库:存储数据的'仓库',保存了一系列有组织的数据 DBMS(Database Management System)数据库管理系统:用于创建或管理DB SQL(Structu ...
- LeetCode替换空格
LeetCode 替换空格 题目描述 请实现一个函数,把字符串 s 中的每个空格替换成"%20". 实例 1: 输入:s = "We are happy." 输 ...
- A Child's History of England.7
After the death of Ethelbert, Edwin, King of Northumbria [公元616年,隋朝末年], who was such a good king tha ...
- Hadoop 相关知识点(二)
1.HDFS副本机制 Hadoopde 默认副本布局策略是: (1)在运行客户端的节点上放置第一个副本(如果客户端运行在集群之外,就随机选择一个节点,不过系统会避免选择那些存储太满或者太忙的节点): ...
- cookie规范(RFC6265)翻译
来源:https://github.com/renaesop/blog/issues/4 RFC 6265 要点翻译 1.简介 本文档定义了HTTP Cookie以及HTTP头的Set-Cookie字 ...
- MySQL自我保护参数
上文(MySQL自我保护工具--pt-kill )提到用pt-kill工具来kill相关的会话,来达到保护数据库的目的,本文再通过修改数据库参数的方式达到阻断长时间运行的SQL的目的. 1.参数介绍 ...