1423. 可获得的最大点数
题目
几张卡牌 排成一行,每张卡牌都有一个对应的点数。点数由整数数组 cardPoints 给出。
每次行动,你可以从行的开头或者末尾拿一张卡牌,最终你必须正好拿 k 张卡牌。
你的点数就是你拿到手中的所有卡牌的点数之和。
给你一个整数数组 cardPoints 和整数 k,请你返回可以获得的最大点数。
示例
输入:cardPoints = [1,2,3,4,5,6,1], k = 3
输出:12
解释:第一次行动,不管拿哪张牌,你的点数总是 1 。但是,先拿最右边的卡牌将会最大化你的可获得点数。最优策略是拿右边的三张牌,最终点数为 1 + 6 + 5 = 12 。
思路
既然求两端的k个值的最大值,可以反过来思考,我们可以求中间的数组连续部分的最小值。问题就转换为,当窗口大小为n-k时,求滑动窗口中的最小值。
代码
public static int maxScore(int[] cardPoints, int k) {
int n = cardPoints.length;
int S[] = new int[n];
S[0] = cardPoints[0];
//求前缀和
for(int i=1;i<n;i++){
S[i] = S[i-1]+cardPoints[i];
}
int m = Integer.MAX_VALUE;
if(n==k){
return S[n-1];
}
for(int i=n-k-1;i<n;i++){
if(i==n-k-1){
m = S[i];
continue;
}
// S[i]-S[i-(n-k)]表示从i-(n-k)+1到i之间数据的和
m = m>(S[i]-S[i-(n-k)])?(S[i]-S[i-(n-k)]):m;
}
//最后,要返回S[n-1]-m,因为要还原成原问题
return S[n-1]-m;
}
补充
这题还有DP的解法,但是我的DP解法超时了,也没找到优化的方法,放一下思路吧
思路是这样的。
定义
f
[
i
]
[
j
]
[
k
]
f[i][j][k]
f[i][j][k]表示在
c
a
r
d
P
o
i
n
t
s
[
i
:
j
]
cardPoints[i:j]
cardPoints[i:j]子数组中,选择k个元素,所能达到的最大值(这里i和j都是包含在数组内的,为了方便,f数组定义为n+1和n+1的)。
那么状态转移方程就是
f
[
i
]
[
j
]
[
k
+
1
]
=
m
a
x
{
f
[
i
+
1
]
[
j
]
[
k
]
+
A
[
i
]
,
f
[
i
]
[
j
−
1
]
[
k
]
+
A
[
j
]
}
f[i][j][k+1] = max\{ f[i+1][j][k]+A[i], f[i][j-1][k]+A[j]\}
f[i][j][k+1]=max{f[i+1][j][k]+A[i],f[i][j−1][k]+A[j]}
当然,上面的方程是有限制的,比如
j
>
=
i
j>=i
j>=i,如果
j
−
i
+
1
<
k
,
t
h
e
n
c
o
n
t
i
n
u
e
;
j-i+1<k, then \quad continue;
j−i+1<k,thencontinue;等等吧。代码如下:
public static int maxScore(int[] cardPoints, int k) {
int n = cardPoints.length;
int f[][][] = new int[n+1][n+1][k+1];
int index =1;
while(index<=k){
for(int i=1;i<=n;i++){
for(int j=i;j<=n;j++){
if(j-i+1<index){
continue;
}
if(index==1){
if(i==j){
f[i][j][index] = cardPoints[i-1];
}else{
f[i][j][index] = (cardPoints[i-1])>(cardPoints[j-1])?(cardPoints[i-1]):(cardPoints[j-1]);
}
}else{
f[i][j][index] = (f[i+1][j][index-1] +cardPoints[i-1])>(f[i][j-1][index-1] +cardPoints[j-1])?(f[i+1][j][index-1] +cardPoints[i-1]):(f[i][j-1][index-1] +cardPoints[j-1]);
}
}
}
index++;
}
return f[1][n][k];
}
但是,这样的话,需要开一个三维数组,很浪费内存。可以直接用二维数据代替,只不过,第二层循环的遍历需要从尾到头,不再是从头到尾,代码如下:
public static int maxScore(int[] cardPoints, int k) {
int n = cardPoints.length;
int f[][] = new int[n+1][n+1];
int index =1;
while(index<=k){
for(int i=1;i<=n;i++){
for(int j=n;j>=i;j--){
if(j-i+1<index){
continue;
}
if(index==1){
if(i==j){
f[i][j] = cardPoints[i-1];
}else{
f[i][j] = (cardPoints[i-1])>(cardPoints[j-1])?(cardPoints[i-1]):(cardPoints[j-1]);
}
}else{
f[i][j] = (f[i+1][j]+cardPoints[i-1])>(f[i][j-1] +cardPoints[j-1])?(f[i+1][j] +cardPoints[i-1]):(f[i][j-1] +cardPoints[j-1]);
}
}
}
index++;
}
return f[1][n];
}