题目
有一排正数,玩家A和玩家B都可以看到。
每位玩家在拿走数字的时候,都只能从最左和最右的数中选择一个。
玩家A先拿,玩家B再拿,两人交替拿走所有的数字,
两人都力争自己拿到的数的总和比对方多。请返回最后获胜者的分数。
例如:
5,2,3,4
玩家A先拿,当前他只能拿走5或者4。
如果玩家A拿走5,那么剩下2,3,4。轮到玩家B,此时玩家B可以选择2或4中的一个,…
如果玩家A拿走4,那么剩下5,2,3。轮到玩家B,此时玩家B可以选择5或3中的一个,…
思路:
首先需要理解这个“两人力争自己拿到的数的总和比对方多”的含义,它说明两人选择数的时候不是只是瞅着当前数组两侧的最大的数,而是需要长远的考虑,即我可以不拿当前最大的数,但我最终的和要比你的大,这就是这句话的含义。
所以我首先能想出来的代码如下:
class Solution
{
public:
int static Max_mark(int a[], int len)
{
if (a == nullptr || len == 0)
{
return 0;
}
return max(before_get(a,0,len-1),after_get(a,0,len-1));
}
int static before_get(int a[], int i, int j) //先手取大的
{
if (i == j)
{
return a[i];
}
else
{
return max(a[i] + after_get(a, i + 1, j), a[j] + after_get(a, i, j - 1));
}
}
int static after_get(int a[], int i, int j) //后手的只能取小的,也只去得到小的
{
if (i == j)
{
return 0;
}
else
{
return min(before_get(a, i + 1, j), before_get(a, i, j - 1));
}
}
};
优化:
上面那种方法使用的是暴力递归的思路。
我们分析一下:
如图:我们假设有一个数组
int a[5] = {1,3,5,4,2};
(以下简称before_get为f函数,after_get为s函数)
所以以上面的方法来做和简化为:
return max(f(a,0,4),s(a,0,4));
因为a在整个程序运行的过程中是不变的,所以可以再次简化为(伪代码方便理解):
return max(f(0,4),s(0,4))
根据f函数的内容决定f(0,4)的时s(1,4)和s(0,3),决定s(1,4)的是f(2,4)和f(1,3)
f(0,4)
s(1,4) s(0,3)
f(2,4) f(1,3) f(0,2)
看到没有,s(1,4)和s(0,3)都依赖于f(1,3)。所以,如果单纯的使用递归的话会有大量的重复计算的问题,所以我们可以使用动态规划来优化时间复杂度。
static int Max_mark_2(int a[], const int len)
{
if (a == nullptr || len == 0)
{
return 0;
}
int** f = new int* [len];
int** s = new int* [len];
for (int i = 0; i < len; i++)
{
f[i] = new int[len];
s[i] = new int[len];
}
for (int i = 0; i < len; i++)
{
for (int j = 0; j < len; j++)
{
f[i][j] = 0;
s[i][j] = 0;
}
}
for (int j = 0; j < len; j++)
{
f[j][j] = a[j];
for (int i = j-1; i>=0 ; i--)
{
f[i][j] = max(a[i] + s[i + 1][j], a[j] + s[i][j - 1]);
s[i][j] = min(f[i][j - 1], f[i + 1][j]);
}
}
return max(f[0][len - 1], s[0][len - 1]);
}
核心步骤是:
for (int j = 0; j < len; j++)
{
f[j][j] = a[j];
for (int i = j-1; i>=0 ; i--)
{
f[i][j] = max(a[i] + s[i + 1][j], a[j] + s[i][j - 1]);
s[i][j] = min(f[i][j - 1], f[i + 1][j]);
}
}
这是啥意思呢?我们分析一下,如图我们要得到的数据是f(0,4)和s(0,4),两者解的过程类似,单独以f(0,4)举例就行了,如上述代码,当j=0,循环只是给f[0][0]赋了个值,当j=1的时候,给f[0][1]和s[0][1]赋了值……当j=5时,赋值的循序为f[4][3]、f[4][2]、 f[4][1]、f[4][0],最终得到了我们所需要的f[4][0](和s[4][0])
这就是动态规划的思想。
当然上面的动态规划还可以继续优化,比如把s函数带入f函数,之后不需要讨论两张表f表和s表,只要讨论一张表,这是一个思路,读者可以自行思考。