#LeetCode 486: Predict the Winner 解题与思考
##题目描述
一组数字,两人轮流去取,每次只能取头部或者尾部,直到把所有数字取完,拿到最大的数字和的获胜,返回先手的人是否可以获胜。
##思路
这个问题和人工智能中的一个最大最小搜索算法类似,有兴趣的可以查找。
解决问题的突破口在于,确立先后手概念。
也就是说,在第一个人(不妨命名为A)先选择一个数字后,他就相当于在剩余的数列中,进行后手选择的;而另外一个人B,则变成了在剩余数列中先手选择的。
而双方选择都建立在一个前提,就是尽可能使自己最终获得的分数最大。
那么就可以得到先后手得分的计算方式。
先手能得到的最大分数,是选择一个数字后加上剩余数列中后手所能选得分数的最大值。
后手所能得到的最大分数,是根据先手选择完成之后,在剩余数列中先手选择所能得到分数的最大值。
于是乎我们用两个数组进行DP求解。
##算法
设在第i个数以及第i+j个数之间先手选择的最大得分为
f
i
r
s
t
_
p
i
c
k
(
i
,
i
+
j
)
first\_pick(i, i+j)
first_pick(i,i+j),后手选择的最大得分为
s
e
c
o
n
d
_
p
i
c
k
(
i
,
i
+
j
)
second\_pick(i,i+ j)
second_pick(i,i+j),第i个数为
n
u
m
(
i
)
num(i)
num(i),选择第一个数的最大得分为
s
e
l
e
c
t
_
f
i
r
s
t
_
o
n
e
select\_first\_one
select_first_one,选择最后一个数的最大得分为
s
e
l
e
c
t
_
l
a
s
t
_
o
n
e
select\_last\_one
select_last_one(注意这里的第一个和最后一个是相对于i, i + j而言的),那么:
s
e
l
e
c
t
_
f
i
r
s
t
_
o
n
e
=
n
u
m
(
i
)
+
s
e
c
o
n
d
_
p
i
c
k
(
i
+
1
,
i
+
j
)
select\_first\_one = num(i) + second\_pick(i + 1,i + j)
select_first_one=num(i)+second_pick(i+1,i+j)
s
e
l
e
c
t
_
l
a
s
t
_
o
n
e
=
n
u
m
(
i
+
j
)
+
s
e
c
o
n
d
_
p
i
c
k
(
i
,
i
+
j
−
1
)
select\_last\_one = num(i + j) + second\_pick(i, i + j - 1)
select_last_one=num(i+j)+second_pick(i,i+j−1)
若$select_first_one > select_last_one $,那么意味着先手选第一个数能得到最大分数,后手在第i+1到i+j之间先手取最大分数
f
i
r
s
t
_
p
i
c
k
(
i
,
i
+
j
)
=
s
e
l
e
c
t
_
f
i
r
s
t
_
o
n
e
first\_pick(i, i+j) = select\_first\_one
first_pick(i,i+j)=select_first_one
s
e
c
o
n
d
_
p
i
c
k
(
i
,
i
+
j
)
=
f
i
r
s
t
_
p
i
c
k
(
i
+
1
,
i
+
j
)
second\_pick(i,i+ j) = first\_pick(i + 1, i+j)
second_pick(i,i+j)=first_pick(i+1,i+j)
若$select_first_one < select_last_one $,那么意味着先手选最后一个数能得到最大分数,后手在第i到i+j - 1之间先手取最大分数
f
i
r
s
t
_
p
i
c
k
(
i
,
i
+
j
)
=
s
e
l
e
c
t
_
l
a
s
t
_
o
n
e
first\_pick(i, i+j) = select\_last\_one
first_pick(i,i+j)=select_last_one
s
e
c
o
n
d
_
p
i
c
k
(
i
,
i
+
j
)
=
f
i
r
s
t
_
p
i
c
k
(
i
,
i
+
j
−
1
)
second\_pick(i,i+ j) = first\_pick(i, i+j-1)
second_pick(i,i+j)=first_pick(i,i+j−1)
这里为了更新的顺序,以j为外层循环,i为内层循环,保证每个需要用到的值在使用之前均能够被计算出来。
最终计算 f i r s t _ p i c k ( 0 , n − 1 ) first\_pick(0, n - 1) first_pick(0,n−1)是否大于 s e c o n d _ p i c k ( 0 , n − 1 ) second\_pick(0, n - 1) second_pick(0,n−1)即可
##代码
#include <iostream>
#include <vector>
#include <stdlib.h>
#include<string.h>
using namespace std;
class Solution {
public:
bool PredictTheWinner(vector<int>& nums) {
int length = nums.size();
int *first_pick = (int*)malloc(length * length * sizeof(int));
memset(first_pick, 0, length * length * sizeof(int));
int *second_pick = (int*)malloc(length * length * sizeof(int));
memset(second_pick, 0, length * length * sizeof(int));
for ( int i = 0; i < length; i++ ) {
first_pick[i * length + i] = nums[i];
second_pick[i * length + i] = 0;
}
for ( int j = 1; j < length; j++ ) {
for ( int i = 0; i < length - j; i++ ) {
int select_first_one = nums[i] + second_pick[(i + 1) * length + i + j];
int select_last_one = nums[i + j] + second_pick[i * length + i + j - 1];
if ( select_first_one > select_last_one ) {
first_pick[i * length + (i + j)] = select_first_one;
second_pick[i * length + (i + j)] = first_pick[(i + 1)*length + (i + j)];
}
else {
first_pick[i * length + (i + j)] = select_last_one;
second_pick[i * length + (i + j)] = first_pick[(i)*length + (i + j) - 1];
}
}
}
bool result = first_pick[length - 1] >= second_pick[length - 1];
free(first_pick);
free(second_pick);
return result;
}
};
##思考
最后还是没能一遍AC,因为当数据为一个0时,本来是平局,但是标准测例还是算了先手获胜,所以最后的大于号换成了大于等于号,问题解决