作者: 负雪明烛
id: fuxuemingzhu
个人博客: http://fuxuemingzhu.cn/


题目地址:https://leetcode.com/problems/stone-game/description/

题目描述

Alex and Lee play a game with piles of stones. There are an even number of piles arranged in a row, and each pile has a positive integer number of stones piles[i].

The objective of the game is to end with the most stones. The total number of stones is odd, so there are no ties.

Alex and Lee take turns, with Alex starting first. Each turn, a player takes the entire pile of stones from either the beginning or the end of the row. This continues until there are no more piles left, at which point the person with the most stones wins.

Assuming Alex and Lee play optimally, return True if and only if Alex wins the game.

Example 1:

Input: [5,3,4,5]
Output: true
Explanation:
Alex starts first, and can only take the first 5 or the last 5.
Say he takes the first 5, so that the row becomes [3, 4, 5].
If Lee takes 3, then the board is [4, 5], and Alex takes 5 to win with 10 points.
If Lee takes the last 5, then the board is [3, 4], and Alex takes 4 to win with 9 points.
This demonstrated that taking the first 5 was a winning move for Alex, so we return true.

Note:

  1. 2 <= piles.length <= 500
  2. piles.length is even.
  3. 1 <= piles[i] <= 500
  4. sum(piles) is odd.

题目大意

有一个数组,两人玩游戏,可以从这个数组的开头或者结尾选择一个数字拿走,拿走以后,另一个人可以继续拿。问,先拿的那个人是否会赢。

解题方法

数学

直接return True就行。因为题目给了限定条件,总和是奇数,数字的个数是偶数。这样也就是简化成了问第一个人拿到的数字总和能否超过sum/2.

所以,第一个人直接选择偶数位置或者奇数位置的数字可以。

比如Alex选择偶数,piles[0], piles[2], …, piles[n-2],
他选择了piles[0],这个时候Lee可以选择piles[1] 或 piles[n - 1].
之后Alex可以继续选择偶数的位置。所以Lee就被迫选择了所有奇数的位置。

反之,如果Alex从倒数第一个开始选,那么他能选到所有的奇数位置,Lee被迫选偶数位置。

故,Alex只要选出奇数、偶数位置中求和之后最大的就行,一定会赢。

class Solution:
def stoneGame(self, piles):
return True

双函数

使用递归求解。这个解法是左程云的算法讲解。

思路就是,作为先选的人,要选择从前面选和从后面选两种方案中的最大值。
作为后选的人,要选择前面选和从后面选两种方案中的最小值。

alex是先选的,所以调用f函数判断他能否赢。

直接递归超时,所以我是用了记忆化搜索减少了时间,就能通过了。

代码如下:

class Solution(object):

    def stoneGame(self, piles):
"""
:type piles: List[int]
:rtype: bool
"""
if not piles:
return False
self.F = [[0 for i in range(len(piles))] for j in range(len(piles))]
self.S = [[0 for i in range(len(piles))] for j in range(len(piles))]
_sum = sum(piles)
alex = self.f(piles, 0, len(piles) - 1)
return alex > _sum / 2 def f(self, piles, i, j):
"""
先选
"""
if i == j:
return piles[i]
if self.F[i][j] != 0:
return self.F[i][j]
curr = max(piles[i] + self.s(piles, i + 1, j), piles[j] + self.s(piles, i, j - 1))
self.F[i][j] = curr
return curr def s(self, piles, i, j):
"""
后选
"""
if i == j:
return 0
if self.S[i][j] != 0:
return self.S[i][j]
curr = min(self.f(piles, i + 1, j), self.f(piles, i, j - 1))
self.S[i][j] = curr
return curr

使用map来完成记忆化搜索,也能通过:

class Solution(object):

    def stoneGame(self, piles):
"""
:type piles: List[int]
:rtype: bool
"""
self.f_map, self.s_map = dict(), dict()
_sum = sum(piles)
alex = self.f(piles, 0, len(piles)-1)
print(alex, _sum)
return alex > _sum / 2.0 def f(self, piles, start, end):
if start == end:
return piles[start]
if (start, end) not in self.f_map:
f_val = max(piles[start] + self.s(piles, start+1, end), piles[end] + self.s(piles, start, end-1))
self.f_map[(start, end)] = f_val
return self.f_map[(start, end)] def s(self, piles, start, end):
if start == end:
return 0
if (start, end) not in self.s_map:
s_val = min(self.f(piles, start+1, end), self.f(piles, start, end-1))
self.s_map[(start, end)] = s_val
return self.s_map[(start, end)]

单函数 + 记忆化递归

使用score函数表示Alex能比Lee多选的分数。可能比双函数更简洁易懂了。

记忆化递归的缺点:1.有可能爆栈;2.无法降维,而DP是可以降维的。

我写的是cpp代码:

class Solution {
public:
bool stoneGame(vector<int>& piles) {
const int N = piles.size();
m_ = vector<vector<int>>(N, vector<int>(N, INT_MIN));
return score(piles, 0, N - 1) > 0;
}
private:
vector<vector<int>> m_;
//Alex比Lee多的分数
int score(vector<int>& piles, int l, int r) {
if (l == r) return piles[l];
if (m_[l][r] == INT_MIN) {
m_[l][r] = max(piles[l] - score(piles, l + 1, r),
piles[r] - score(piles, l, r - 1));
}
return m_[l][r];
}
};

动态规划

动态规划解法比较难想,dp数组的第i个位置表示的是从第i个石头到第i+l-1个石头之间最大的比对手得分。

使用的是一个长度变量和起始索引,计算每个位置开始的长度1~N长度的区间的dp状态。

class Solution {
public:
bool stoneGame(vector<int>& piles) {
const int N = piles.size();
// dp[i] := max(your_stones - op_stones) for piles[i] to piles[i + l - 1]
vector<vector<int>> dp(N, vector<int>(N, INT_MIN));
for (int i = 0; i < N; i++)
dp[i][i] = piles[i];
for (int l = 2; l <= N; l++) {
for (int i = 0; i < N - l + 1; i++) {
int j = i + l - 1;
dp[i][j] = max(piles[i] - dp[i + 1][j], piles[j] - dp[i][j - 1]);
}
}
return dp[0][N - 1] > 0;
}
};

参考资料:

https://leetcode.com/problems/stone-game/discuss/154610/C++JavaPython-DP-or-Just-return-true

日期

2018 年 9 月 4 日 —— 迎接明媚的阳光!
2018 年 12 月 4 日 —— 周二啦!

【LeetCode】877. Stone Game 解题报告(Python & C++)的更多相关文章

  1. 【LeetCode】120. Triangle 解题报告(Python)

    [LeetCode]120. Triangle 解题报告(Python) 作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 题目地址htt ...

  2. LeetCode 1 Two Sum 解题报告

    LeetCode 1 Two Sum 解题报告 偶然间听见leetcode这个平台,这里面题量也不是很多200多题,打算平时有空在研究生期间就刷完,跟跟多的练习算法的人进行交流思想,一定的ACM算法积 ...

  3. 【LeetCode】Permutations II 解题报告

    [题目] Given a collection of numbers that might contain duplicates, return all possible unique permuta ...

  4. 【LeetCode】Island Perimeter 解题报告

    [LeetCode]Island Perimeter 解题报告 [LeetCode] https://leetcode.com/problems/island-perimeter/ Total Acc ...

  5. 【LeetCode】01 Matrix 解题报告

    [LeetCode]01 Matrix 解题报告 标签(空格分隔): LeetCode 题目地址:https://leetcode.com/problems/01-matrix/#/descripti ...

  6. 【LeetCode】Largest Number 解题报告

    [LeetCode]Largest Number 解题报告 标签(空格分隔): LeetCode 题目地址:https://leetcode.com/problems/largest-number/# ...

  7. 【LeetCode】Gas Station 解题报告

    [LeetCode]Gas Station 解题报告 标签(空格分隔): LeetCode 题目地址:https://leetcode.com/problems/gas-station/#/descr ...

  8. LeetCode: Unique Paths II 解题报告

    Unique Paths II Total Accepted: 31019 Total Submissions: 110866My Submissions Question Solution  Fol ...

  9. Leetcode 115 Distinct Subsequences 解题报告

    Distinct Subsequences Total Accepted: 38466 Total Submissions: 143567My Submissions Question Solutio ...

随机推荐

  1. Git分布式版本控制系统基础

    查看创建的账号 下来在该当前的⽬录下创建⽂件,并且进⾏提交 使⽤git log就可以看到最近提交的⽇志记录的信息 查看窗户的状态信息 某些时候我们可能需要回退到之前的版本,那么具体处理的步骤为: 1. ...

  2. 05 Windows安装python3.6.4+pycharm环境

    windows安装python3.6.4环境 使用微信扫码关注微信公众号,并回复:"Python工具包",免费获取下载链接! 一.卸载python环境 卸载以下软件: 二.安装py ...

  3. 半天做完的数据报表,YonBuilder只要十几分钟,0代码开发

    进入数字化时代,拍脑袋的决策方式显然不靠谱,一切要靠数据说话.与信息化时代相比,数字化时代的企业对数据的应用更广泛.更深入.为了应对激烈的市场竞争,企业经营决策者们对数据的依赖度越来越高,企业各个业务 ...

  4. day18定时任务

    day18定时任务 什么是定时任务 类似日常生活之中的闹钟:主要用于定时执行某些命令,达到定时处理数据的作用. 作用: 1.类似生活中使用的闹钟 2.可以自动完成操作命令 3.定时备份系统数据信息 定 ...

  5. 零基础学习java------40---------Maven(maven的概念,安装,maven在eclipse中使用),springboot(spring整合springmvc(注解),spring整合mybatis(常见的配置文件)),前端页面(bootstrap软件)

    一 maven 1. Maven的相关概念 1.1 项目开发中遇到的问题 (1)都是同样的代码,为什么在我的机器上可以编译执行,而在他的机器上就不行? (2)为什么在我的机器上可以正常打包,而配置管理 ...

  6. [php安全]原生类的利用

    php原生类的利用 查看原生类中具有魔法函数的类 $classes = get_declared_classes(); foreach ($classes as $class) { $methods ...

  7. 【STM32】使用SDIO进行SD卡读写,包含文件管理FatFs(八)-认识内存管理

    [STM32]使用SDIO进行SD卡读写,包含文件管理FatFs(一)-初步认识SD卡 [STM32]使用SDIO进行SD卡读写,包含文件管理FatFs(二)-了解SD总线,命令的相关介绍 [STM3 ...

  8. malloc() vs new

    Following are the differences between malloc() and operator new. (1)new calls constructors, while ma ...

  9. Spring Boot 自动扫描组件

    使用@ComponentScan自动扫描组件 案例准备 1.创建一个配置类,在配置类上添加 @ComponentScan 注解.该注解默认会扫描该类所在的包下所有的配置类,相当于之前的 <con ...

  10. Spring组合注解与元注解

    目录 注解说明 源代码 使用范例 注解说明 元注解:可以注解到别的注解上的注解,所以元注解首先基于条件@Target({ElementType.TYPE}) ,目标使用在类文件上 . 组合注解:连个元 ...