作者: 负雪明烛
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. java数组中Arrays类

    使用Arrays类之后要先导入包,即在开头添加这行: import.java.util.Arrays 1,排序:Arrays.sort(数组名) 排序后为数组升序. 2,将数组转换成字符串:Array ...

  2. git放弃修改,强制覆盖本地代码

    1.git fetch --all  //从远程拉取最新的代码 不merge 2.git reset --hard origin/develop  //使用指定分支的代码(此处develop)强制覆盖 ...

  3. mongoDB整个文件夹拷贝备份还原的坑

    现网有一个mongoDB数据库需要搬迁到新服务器,开发那边的要求是先搬迁现在的数据库过去,然后剩下的以后他们用程序同步. 数据库大楷20G左右,现网是主备仲裁的,停掉备点,拷贝了全部文件. 新服务器也 ...

  4. 宏GENERATED_BODY做了什么?

    Version:4.26.2 UE4 C++工程名:MyProject \ 一般语境下,我们说c++源码的编译大体分为:预处理.编译.链接; cppreference-translation_phas ...

  5. C#多个标题头合并

    protected void GridView1_RowCreated(object sender, GridViewRowEventArgs e) { switch (e.Row.RowType) ...

  6. 3 - 简单了解一下springboot中的yml语法 和 使用yml赋值

    1.简单了解yml语法 2.使用yml给实体类赋值 准备工作:导入依赖 <!-- 这个jar包就是为了实体类中使用@ConfigurationProperties(prefix = " ...

  7. SQLITE_BTREE_H

    sqlite3.c, 237436行 = 全部源文件,找东西比多文件查找方便多了:-),字符串查找一点都不慢. 不要太害怕,SQLite说它的代码里有非常多是用来做数据完整性检查和测试的.但愿B树,虚 ...

  8. day06 视图层

    day06 视图层 今日内容 视图层 小白必会三板斧 JsonResponse form表单发送文件 FBV与CBV FBV基于函数的视图 CBV基于类的视图 模板层 模板语法的传值 模板语法之过滤器 ...

  9. D3学习-加载本地数据

    在加载本地数据时,弄了很久都无法显示出来,后来才知道是要把数据文件和html文件都加载到服务器上面 这样就可以显示出来了,

  10. linux vi(vim)常用命令汇总(转)

    前言 首先解析一个vim vi是unix/linux下极为普遍的一种文本编辑器,大部分机器上都有vi的各种变种,在不同的机器上常用不同的变种软件,其中vim比较好用也用的比较广泛.vim是Vi Imp ...