从递归开始入手区间DP
博客园,启动
题目:预测赢家
题目链接:
https://leetcode.cn/problems/predict-the-winner/description/
题面:
给你一个整数数组 nums 。玩家 1 和玩家 2 基于这个数组设计了一个游戏。
玩家 1 和玩家 2 轮流进行自己的回合,玩家 1 先手。开始时,两个玩家的初始分值都是 0 。每一回合,玩家从数组的任意一端取一个数字(即,nums[0] 或 nums[nums.length - 1]),取到的数字将会从数组中移除(数组长度减 1 )。玩家选中的数字将会加到他的得分上。当数组中没有剩余数字可取时,游戏结束。
如果玩家 1 能成为赢家,返回 true 。如果两个玩家得分相等,同样认为玩家 1 是游戏的赢家,也返回 true 。你可以假设每个玩家的玩法都会使他的分数最大化。
这道题是很经典的从两端进行可能性展开的区间DP,下面会从暴力递归-记忆化搜索-区间DP逐步对这道题进行分析。
刚拿到这道题以为是贪心,但是很容易就能明白贪心都会有很明显的错误
如样例:10 100 40 2,很明显最优解是拿走2和100。无论怎么贪心都是实现不了的
暴力递归
分析题目,我们可以发现题干有很明显的零和博弈,我们在设计递归时要兼顾玩家1和玩家2的博弈行为
设计递归函数
int dfs(int left,int right);
意为进行该玩家1进行操作时还有[left,right]范围的数还没有被选择。
在进行递归时,会出现如下几种情况
- 当left==right时,就剩下一个数没选,所以玩家1会毫不留情的拿走。
- 当left+1==right时,剩下两个数没选,所以玩家1会将两个数中的最大值拿走。
- 一般情况。
我们现在对一般情况进行讨论。
如果玩家1拿走了left位置上的数的话,那么玩家1下一次选择的可能会有
- 玩家2拿走了left+1位置的元素,玩家1从left+2与right位置上的数中选一个
- 玩家2拿走了high位置上的元素,玩家1从left+1与right-1位置上的数选一个。
当玩家2拿走right位置上的元素的情况就不过多赘述。
那我们如何实现玩家1和玩家2之间的博弈操作?
试想以下,如果玩家2也想取得最大值的话是不是等同于让玩家1尽可能取得最小值。
所以当玩家1在选择某个数后玩家2让其获得玩家1下一次进行的两次操作的最小值就能实现博弈操作。
代码如下
点击查看代码
int dfs(int low, int high)
{
if (low == high)
return arr[low];
if (low + 1 == high)
return max(arr[low], arr[high]);
int p1 = min(dfs(low + 1, high - 1) + arr[low], dfs(low + 2, high) + arr[low]);
int p2 = min(dfs(low + 1, high - 1) + arr[high], dfs(low, high - 2) + arr[high]);
return max(p1, p2);
}
记忆化搜索?挂个缓存表就行,不过多赘述。
准备了那么多,接下来就是如何将递归转化为动态规划。
- 首先是根据递归初始化数组,参考上面第一和第二种情况。
- 搞清楚数组中某一位置对其他位置的依赖关系,从而决定循环进行的方向。
就以这道题为例
我们在得到某一位置(用A来表示)的答案之前就要得到对应位置的答案(用B来表示)
| 如 | 果 | 感 | 觉 | 不 |
|---|---|---|---|---|
| B | A | |||
| B | ||||
| B | ||||
| 错 | 请 | 点 | 个 | 赞 |
位置依赖根据写的具体的递归操作来决定
浙公网安备 33010602011771号