1423. 可获得的最大点数---滑动窗口篇七,前缀和篇三

Posted 大忽悠爱忽悠

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了1423. 可获得的最大点数---滑动窗口篇七,前缀和篇三相关的知识,希望对你有一定的参考价值。

在这里插入图片描述
在这里插入图片描述

可获得的最大点数题解集合


递归

思路:

你是不是跟我一样,拿到今天题目的第一想法是模拟题目取卡牌的过程呢?模拟的方法可以用递归。但是递归的过程是把所有的可能组合方式都求了一遍,时间复杂度会达到 O(N*k) ,在题目所给出的 10 ^ 5 的数据规模下,会超时。

下面的代码是我用的递归+记忆化的方式写的,虽然有记忆化,但是因为没有降低时间复杂度,所以仍然超时。提供在这里仅供大家参考。欢迎大家提供能 AC 的递归方法。

我定义的递归函数 dfs(cardPoints, i, j, k) ,表示在 cardPoints 的第 i ~ j 的位置中(包含i,j),从两端抽取 k 个卡牌能够获得的最大点数。那么当 k == 0 的时候,说明不抽牌,结果是 0。当 k != 0 的时候,抽取 k 个卡牌能拿到的点数等于 max(抽取最左边卡牌的点数 + 剩余卡牌继续抽获得的最大点数, 抽取最右边卡牌的点数 + 剩余卡牌继续抽获得最大点数)。

图解
在这里插入图片描述

代码:

struct hash_pair {
	template <class T1, class T2>
	size_t operator()(const pair<T1, T2>& p) const
	{
		auto hash1 = hash<T1>{}(p.first);
		auto hash2 = hash<T2>{}(p.second);
		return hash1 ^ hash2;
	}
};
class Solution {
	unordered_map<pair<int, int>, int,hash_pair> cache;
public:
	int maxScore(vector<int>& cardPoints, int k) 
	{
		return dfs(cardPoints, k, 0, cardPoints.size()-1);
	}
	int dfs(vector<int>& cardPoints, int k, int l, int r)
	{
		//当前无卡牌可选择
		if (k == 0) return 0;
		//当前对应的状态的最大和已经求出,那么直接返回即可
		if (cache.find({ l,r }) != cache.end()) return cache[{l, r}];
		//计算当前选择最左边第一张牌
		int selLeft = cardPoints[l] + dfs(cardPoints, k - 1, l + 1, r);
		//选择最右边第一张牌
		int selRight = cardPoints[r] + dfs(cardPoints, k - 1, l, r-1);
		//从这两种选择中挑选和最大的那一种
		return cache[{l, r}] = max(selLeft, selRight);
	}
};

在这里插入图片描述


前缀和

当数据规模到达了 10 ^ 5 ,已经在提醒我们这个题应该使用 O(N) 的解法。

把今天的这个问题思路整理一下,题目等价于:求从 cardPoints 最左边抽 i 个数字,从 cardPoints 最右边抽取 k - i 个数字,能抽取获得的最大点数是多少。

一旦这么想,立马柳暗花明:抽走的卡牌点数之和 = cardPoints 所有元素之和 - 剩余的中间部分元素之和。

我们同样使用模拟法,但是比递归方法高妙的地方在,我们一次性从左边抽走 i 个数字: i 从 0 到 k 的遍历,表示从左边抽取了的元素数,那么从右边抽取的元素数是 k - i 个。现在问题是怎么快速求 剩余的中间部分元素之和?

求区间的和可以用 preSum 。 preSum 方法还能快速计算指定区间段 i ~ j 的元素之和。它的计算方法是从左向右遍历数组,当遍历到数组的 i 位置时, preSum 表示 i 位置左边的元素之和。

假设数组长度为 N ,我们定义一个长度为 N+1 的 preSum 数组, preSum[i] 表示该元素左边所有元素之和(不包含当前元素)。然后遍历一次数组,累加区间 [0, i) 范围内的元素,可以得到 preSum 数组。

代码如下:

		vector<int> preSum(cardPoints.size() + 1, 0);
		for (int i = 1; i <=cardPoints.size(); i++)
			preSum[i] = preSum[i - 1] + cardPoints[i-1];

利用 preSum 数组,可以在 O(1) 的时间内快速求出 nums 任意区间 [i, j] (两端都包含) 的各元素之和。

在这里插入图片描述

综合以上的思路,我们的想法可以先求 preSum ,然后使用一个 0 ~ k 的遍历表示从左边拿走的元素数,然后根据窗口大小 windowSize = N - k ,利用 preSum 快速求窗口内元素之和。

过程图解:
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

代码:

class Solution {
public:
	int maxScore(vector<int>& cardPoints, int k) 
	{
		vector<int> preSum(cardPoints.size() + 1, 0);
		for (int i = 1; i <=cardPoints.size(); i++)
			preSum[i] = preSum[i - 1] + cardPoints[i-1];
		int WS = cardPoints.size() - k;//滑动窗口的大小是固定的
		int res = INT_MAX;
		//找出所有滑动窗口中和最小的一个
		for (int i = 0; i+WS<=cardPoints.size(); i++)
			res=min(res,preSum[i + WS]-preSum[i]);
		return preSum.back()-res;
	}
};

在这里插入图片描述


滑动窗口

在上面的 preSum 中,我们已经想到了,抽走的卡牌点数之和 = cardPoints 所有元素之和 - 剩余的中间部分元素之和。在 preSum 的代码里,我们是模拟了从左边拿走 i 个卡牌的过程。事实上,我们也可以直接求剩余的中间部分元素之和的最小值。只要剩余的卡牌点数之和最小,那么抽走的卡牌点数之和就最大!

求一个固定大小的窗口中所有元素之和的最小值——这是一个滑动窗口问题!与这个问题非常类似的就是643. 子数组最大平均数 I

把剩余的中间部分元素抽象成长度固定为 windowSize = N - k 的滑动窗口。当每次窗口右移的时候,需要把右边的新位置 加到 窗口中的 和 中,把左边被移除的位置从窗口的 和 中 减掉。这样窗口里面所有元素的 和 是准确的,我们求出最大的和,最终除以 k 得到最大平均数。

这个方法只用遍历一次数组。

需要注意的是,需要根据 i 的位置,计算滑动窗口是否开始、是否要移除最左边元素:

  • 当 i >= windowSize 时,为了固定窗口的元素是 k 个,每次移动时需要将 i - windowSize 位置的元素移除。
  • 当 i >= windowSize - 1 时,滑动窗口内的元素刚好是 k 个,开始计算滑动窗口的最小和。

最后,用 cardPoints 的所有元素之和,减去滑动窗口内的最小元素和,就是拿走的卡牌的最大点数。

代码:

class Solution {
public:
	int maxScore(vector<int>& cardPoints, int k) 
	{
		int len = cardPoints.size();
		int sum = 0, res = INT_MAX;
		int WS = len - k;//滑动窗口的固定大小
		for (int i = 0; i < cardPoints.size(); i++)
		{
			sum += cardPoints[i];
			if (i >= WS) sum -= cardPoints[i - WS];//将最左边的元素移除滑动窗口求和范围内
			if (i >= WS - 1) res = min(res, sum);
		}
		return accumulate(cardPoints.begin(), cardPoints.end(), 0) - res;
	}
};

在这里插入图片描述


总结

  • 今天这个题目,难点还是需要把抽取卡牌的问题转变为一个滑动窗口的问题。
  • 问题抽象之后,preSum 和 滑动窗口 两种解法就已经呼之欲出了。

以上是关于1423. 可获得的最大点数---滑动窗口篇七,前缀和篇三的主要内容,如果未能解决你的问题,请参考以下文章

代码随想录算法训练营第13天 | ● 239. 滑动窗口最大值 ● 347.前 K 个高频元素 ● 总结

[LeetCode in Python] 5393 (M) maximum points you can obtain from cards 可获得的最大点数

剑指offer(C++)-JZ59:滑动窗口的最大值(数据结构-队列 & 栈)

剑指offer(C++)-JZ59:滑动窗口的最大值(数据结构-队列 & 栈)

剑指offer(C++)-JZ59:滑动窗口的最大值(数据结构-队列 & 栈)

面试常考算法题(考察单调队列)--滑动窗口的最大值