【Leetcode 877. 石子游戏】DP求解

题面:

亚历克斯和李用几堆石子在做游戏。偶数堆石子排成一行,每堆都有正整数颗石子 piles[i] 。
游戏以谁手中的石子最多来决出胜负。石子的总数是奇数,所以没有平局。
亚历克斯和李轮流进行,亚历克斯先开始。 每回合,玩家从行的开始或结束处取走整堆石头。 这种情况一直持续到没有更多的石子堆为止,此时手中石子最多的玩家获胜。
假设亚历克斯和李都发挥出最佳水平,当亚历克斯赢得比赛时返回 true ,当李赢得比赛时返回 false 。

在本题中,由于本题的博弈性质,可以直接先手必胜,所以答案必然为true
可以直接return true即可。这里提供第二种解决方法。

动态规划求解。

问题最后是需要我们给出最终先手方可不可以必胜。
那么对于全局问题来说。我们只需要计算出每次双方出手后的拿到的石子的差数之和。并且让他最大化,再看最后的值是否为正数即可。
而单次的石子差=先手方拿到数量 - 后手方拿到的数量
最后的总差肯定是所有单次的石子差之和。

我们用两个指针(i,j)分别指向该数组(石子) 的头尾
全局看问题。对于第一手而言。是对从1->length的石头进行挑选头或者尾
那对于第二手而言,就是挑选第一手挑剩下的(1,length-1)或者是(2,length)

因此,该问题具有重叠的子问题
分别讨论三种情况


这里声明:
arr表示的是石堆数组,dptable表示的是差值数组,差值数组并不代表先后顺序


  1. i=j :表示当前两个指针指向同一个石堆,那就说明,当前只剩下一个石堆可以拿了。那当前的手与下一手(当前手拿完就没有了!因为只剩下一个)的差值就是arr[i]或者是arr[j]
  2. i>j :表示当前头指针居然在尾指针的后面,这显然不合理,所以这部分是没用的
  3. i<j : 表示当前头指针在尾指针的前面,这很正常,此时表示当前手与下一手(这个时候下一手就有取值了)的差值,而当前手的值则是arr[i]或者是arr[j]。 所以具有两种情况,如果取了arr[i]则表示我拿了当前数组的头部,反之则拿了尾部。故:

在拿头还是拿尾的抉择中,目的是为了让本次的差值是最大的(贪心策略),那么则需要比较,拿头大还是拿尾大。

  1. 拿头的话,对于下一手而言可取范围就从[i,j]变成了[i+1,j]
  2. 拿尾部的话,对于下一手而言可取范围就从[i,j]变成了[i,j-1]

而对于本手的下一手方,又自然会选择最大的(注意,此时的i,j已经不再是第一手的i,j了)

  1. 拿头的话,对于下一手而言可取范围就从[i,j]变成了[i+1,j]
  2. 拿尾部的话,对于下一手而言可取范围就从[i,j]变成了[i,j-1]

所以就会出现一种递归的情况。

而由于最后一手必然会从i=j的情况下拿到。所以
设,第一手拿x1,第二手拿x2,…,第n手拿xn
最终总差为(x1-x2)+(x3-x4)+…+(xn-1 - xn)
变换一下,则成了
x1-(x2-x3)-(x4-x5)-…-(xn-2 - xn-1)-xn
也就是说:
状态转移方程为
dptable[i][j]=max{
(arr[i]-dp[i+1][j]) ,
(arr[j]-dp[i][j-1])
}

并且dptable[i][j] ( i == j 时 ,0 < i < length )=arr[i]
故有实现

	class Solution {
    public int max(int a,int b){
        if(a>b)return a;
        else return b;
    }

    public boolean stoneGame(int[] piles) {
        int len=piles.length;
        int dptable[][]=new int[len][len];
        for(int i=0;i<len;i++){
            dptable[i][i]=piles[i];
        }
        for(int i=piles.length-1;i>=0;i--){
            for(int j=piles.length-1;j>=0;j--){
                if(j<=i)break;
                else{
                    dptable[i][j]=max(piles[i]-dptable[i+1][j],piles[j]-dptable[i][j-1]);
                }
            }
        }
        if(dptable[0][len-1]>0)return true;
        else return false;
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值