首页 > 试题广场 >

排成一条线的硬币博弈问题

[编程题]排成一条线的硬币博弈问题
  • 热度指数:777 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 32M,其他语言64M
  • 算法知识视频讲解
面值为正数的硬币放置成一排,玩家1和玩家2轮流拿走硬币,规定每个玩家在拿硬币时,只能拿走最左或最右的硬币。 例如: 硬币面值与排列为:1,2,3,4,5,现在轮到玩家1拿硬币。 在当前状态下,玩家1只能拿走1或5, 如果玩家1拿走1,则排列变为2,3,4,5,那么接下来玩家2可以拿走2或5,然后继续轮到玩家1拿硬币... 如果玩家1拿走5,则排列变为1,2,3,4,那么接下来玩家2可以拿走1或4;然后继续轮到玩家1拿硬币... 游戏按照这个规则进行,直到所有的硬币被拿完,每个玩家获得的分数是各自拿走硬币的总和。 游戏胜负的规定: 如果玩家1最后获得的分数大于玩家2,则玩家1获胜; 如果玩家2最后获得的分数大于玩家1,则玩家2获胜; 因为玩家1先拿硬币,所以如果最后两人获得分数一样则玩家2获胜; 给定一个数组arr,表示硬币的面值和排列状况,请返回最终获胜者的分数。 例子: arr=[8,7,5,3] 玩家1将获胜,分数为13 所以返回13 arr=[1,9,1] 玩家2将获胜,分数为9 所以返回9
推荐
简单区间动态规划,dp(l,r)表示在[l,r]这段区间先手能获得的最大值,dp(l,r)=sum(l,r)-min(dp(l+1,r), dp(l, r-1)),答案为max(dp(0,len), sum(0,len)-dp(0,len)),O(N^2)的时间和空间

#define MIN(a, b) ((a) < (b) ? (a) : (b))
class Solution {
public:
 /**
 *	得到硬币博弈问题的获胜分值
 *	输入:代表硬币排列情况的数组arr
 *	返回:硬币博弈问题的获胜分值
 */
 int getWinValue(vector<int> arr, int len) {
        if(len <= 0) return 0;
        vector<int> sum(arr);
        vector<vector<int> > dp(len, vector<int>(len, 0));
        for(int i = 0; i < len; ++i){
            if(i > 0) sum[i] += sum[i - 1];
            dp[i][i] = arr[i];
        }
        for(int L = 1; L < len; ++L) for(int i = 0; i + L < len; ++i){
            int j = i + L;
            dp[i][j] = sum[j] - sum[i] + arr[i] - MIN(dp[i + 1][j], dp[i][j - 1]);
        }
        return sum[len - 1] - MIN(dp[0][len - 1], sum[len - 1] - dp[0][len - 1]);
    }
};

编辑于 2015-08-18 23:40:15 回复(1)
arr=[8,10,3,6,2,3],结果为啥是19?我求的是17.
public int getWinValue(int[] arr) {
        int n = arr.length;
        int low = 0;
        int high = n - 1;
        int turn = 0;
        int max = 0;
        int sum = 0;
        
        for(int i = 0; i < n; i ++){
            sum += arr[i];
        }
        
        while(low <= high){
            if(turn++ % 2 == 0){
                max += Math.max(arr[low], arr[high]);
            }
            
            if(arr[low] > arr[high]){
                low ++;
            }else{
                high --;
            }
        }
        
        return Math.max(max, sum-max);
    }
发表于 2015-04-25 22:01:18 回复(1)
可以用动态规划来解.
用dp[i][j]表示i,j区间内先拿硬币能得到的最大值.因为两个玩家都按照最优策略来玩.
可以推出状态转移方程:
dp[i][j] = max(
    min(A[i] + dp[i + 2][j], A[i] + dp[i + 1][j - 1]), 
    min(A[j] + dp[i + 1][j - 1], A[j] + dp[i][j - 2])
 ).
初始条件: dp[i][i] = A[i], dp[p][q] = 0(p > q).
代码用记忆化搜索写起来比较简单.
class Solution {
private:
    int memorized_dfs(int i, int j, vector<vector<int> > &dp, vector<int> &A) {
        if (i == j)
            return dp[i][j] = A[i];
        else if (i > j)
            return 0;
        
        int v = max(min(A[i] + memorized_dfs(i + 2, j, dp, A),			/* 先手拿左边, 后手拿剩下的左边 */
                        A[i] + memorized_dfs(i + 1, j - 1, dp, A)),		/* 先手拿左边, 后手拿剩下的右边 */
                    min(A[j] + memorized_dfs(i + 1, j - 1, dp, A),		/* 先手拿右边, 后手拿剩下的左边 */
                        A[j] + memorized_dfs(i, j - 2, dp, A)));		/* 先手拿右边, 后手拿剩下的右边 */
        
        return dp[i][j] = v;
    }
public:
 /**
 *	得到硬币博弈问题的获胜分值
 *	输入:代表硬币排列情况的数组arr
 *	返回:硬币博弈问题的获胜分值
 */
 int getWinValue(vector<int> arr, int len) {
 vector<vector<int> > dp(len, vector<int>(len, -1));
        int sum = 0;
        for (int i = 0; i < arr.size(); ++i)
            sum += arr[i];
        
        int first = memorized_dfs(0, len - 1, dp, arr);
        int second = sum - first;
        
        return max(first, second);
    }
};

发表于 2015-04-01 10:42:06 回复(1)
使用递归的方法解决:
public int getWinValue(int[] arr) {
		if(arr.length==1)return arr[0];
		int a[]=getPath(arr, 0, arr.length-1);	
		return a[0]>a[1]?a[0]:a[1];
	}
	//返回子节点的收益以及该节点的收益,第一个数据表示的是子节点的收益,第二个数据表示该节点的收益,使用递归的方式解决
	public int[] getPath(int []arr,int first,int last){
		int t[]=new int[2];
		if(last==first+1){
			if(arr[first]>=arr[last]){
				t[0]=arr[last];
				t[1]=arr[first];
			}
			else{
				t[0]=arr[first];
				t[1]=arr[last];
			}
			return t;
		}
		int a[]=new int[2];
		int b[]=new int[2];
		a=getPath(arr, first+1, last);
		b=getPath(arr, first, last-1);
		if(arr[first]+a[0]>=arr[last]+b[0]){
			t[0]=a[1];
			t[1]=arr[first]+a[0];
		}
		else{
			t[0]=b[1];
			t[1]=arr[last]+b[0];
		}
		return t;
	}

编辑于 2015-03-21 16:08:31 回复(1)
有递归版的,有动态规划版的
先理解递归版的
当递归版的理解后,动态规划版的就好理解了
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class Solution {
    public static Scanner sc = new Scanner(System.in);
    public static int N;
    public static int array[];
    public static int dpfirst[][];
    public static int dpsecond[][];

    public static int first(int left, int right) {
        // 先手
        if (left == right) {
            return array[left];
        } else {
            int a = second(left + 1, right) + array[left];
            int b = second(left, right - 1) + array[right];
            // 想要的是后手的最大的
            return Math.max(a, b);
        }
    }
    public static int second(int left, int right) {
        if (left == right) {
            return 0;
        } else {
            int a = first(left + 1, right);
            int b = first(left, right - 1);
            // 因为自己不能选,只能对方选,因此对方一定会返回给自己更小的数

            return Math.min(a, b);
        }
    }
    public int getWinValue(int[] arr) {
        array = arr;
        N = arr.length;
        dpfirst = new int[N][N];
        dpsecond = new int[N][N];
        for (int left = 0; left < N; left++) {
            for (int right = 0; right < N; right++) {
                dpfirst[left][right] = -1;
                dpsecond[left][right] = -1;
            }
        }
        for (int i = 0; i < N; i++) {
            dpfirst[i][i] = array[i];
            dpsecond[i][i] = 0;
        }
        int count = 1;
        while(count < N){
            for (int i = 0; i < N - count; i++) {
                int left = i;
                int right = i + count;
                int a = dpsecond[left + 1][right] + array[left];
                int b = dpsecond[left][right - 1] + array[right];
                dpfirst[left][right] = Math.max(a, b);
                dpsecond[left][right] = Math.min(dpfirst[left + 1][right], dpfirst[left][right - 1]);
            }
            count++;
        }
        return Math.max(dpfirst[0][N - 1],dpsecond[0][N - 1]);
    }

    
}


发表于 2023-03-06 00:44:18 回复(0)
496头像 496
class Solution {
public:
	/**
	 *	得到硬币博弈问题的获胜分值
	 *	输入:代表硬币排列情况的数组arr
	 *	返回:硬币博弈问题的获胜分值
	 */
	int getWinValue(vector<int> arr, int len) {
        vector<vector<int>> dp(len,vector<int>(len,0));
        vector<int> sum(len,0);
        sum[0] = arr[0];
        for(int i=0;i<len;i++){
            dp[i][i] = arr[i];
            if(i>0)
                sum[i] = arr[i]+sum[i-1];
        }
        int j;
        for(int l=1;l<len;l++)
            for(int i=0;i+l<len;i++){
                j = i+l;
                dp[i][j]=sum[j]-sum[i-1]-min(dp[i+1][j],dp[i][j-1]);
            }
        return dp[0][len-1]>sum[len-1]-dp[0][len-1]?dp[0][len-1]:sum[len-1]-dp[0][len-1];
    }
    int min(int a,int b){
        if(a>b)
            return b;
        else
            return a;
    }
};
dp[l,r]表示先拿者可以获得的最大分数,dp[l,r]=max((sum(l,r)-dp[l+1,r]),sum(l,r)-dp[l,r-1]),sum(l,r)表示该区间上的总分数=sum(r)-sum(l-1),初始dp[i,i]=arr[i]
发表于 2017-02-22 15:38:17 回复(1)
public static int getWinValue(int[] arr){
		int low = 0;
		int higth = arr.length-1;
		int turn=0,sum = 0,max=0;
		
		//计算总面额
		for(int i=0; i<arr.length; i++){
			sum += arr[i];
		}
		
		while(low <= higth){
			//玩家1取数
			if(turn++ %2 == 0){//玩家1取的顺序是第0,2,4,6...次
				max += Math.max(arr[low], arr[higth]);
			}
			
			//移动数组指针
			if(arr[low] > arr[higth]){//说明上一步取的是arr[low]
				low++;
			}
			else if(arr[low] < arr[higth]){//说明上一步取的是arr[higth]
				higth--;
			}
			else if(arr[low] == arr[higth]){//比如arr[8,10,3,2,6,3],玩家1更乐意取左边的3而不是右边的,这样保证下下一步玩家1可取到6
				if(arr[low+1] >= arr[higth-1]){
					higth--;
				}else{
					low++;
				}
			}
			
		}
		
		
		return Math.max(max, sum-max);
	}
	

发表于 2015-07-22 17:18:51 回复(2)
mwq头像 mwq
public class Solution {
	public int getWinValue(int[] arr) {
		int fh=0, sh=0,sum=0;
		for(int i=0;i<arr.length;i++){
			sum+=arr[i];
		}
		fh=getFirstHandMaxValue(arr);
		sh=sum-fh;
		
        
        if(fh>=sh)
            return fh;
        else
            return sh;

	}
	
	private int getFirstHandMaxValue(int[] arr){
		int fh=0;
	
        int length = arr.length;       
        if(length==1){
        	fh=arr[0];
        }else{        
        	int sum1=0;
        	int sum2=0;
        	int[] subarr1=new int[length-1];
        	int[] subarr2=new int[length-1];
        	for(int i=0;i<length-1;i++){
        		subarr1[i]=arr[i+1];
        		subarr2[i]=arr[i];
        		sum2+=arr[i];
        		sum1+=arr[i+1];
        	}
        	
        	fh=Math.max(arr[0]+sum1-getFirstHandMaxValue(subarr1), arr[length-1]+sum2-getFirstHandMaxValue(subarr2));
        	
        }
		return fh;
        }
	
	public static void main(String[] args){
		Solution s = new Solution();
		int[] arr = {1,9,1,9,1};
		System.out.print(s.getWinValue(arr));
	}
}

自己写的可是系统检测和别人的相似了


发表于 2015-07-22 15:01:30 回复(0)
发表于 2015-06-11 20:10:25 回复(0)
class Solution {
public:
    /**
     *  得到硬币博弈问题的获胜分值
     *  输入:代表硬币排列情况的数组arr
     *  返回:硬币博弈问题的获胜分值
     */
    int getWinValue(vector<int> arr, int len) {
         
        int a[len][len],  //a的最高得分
            b[len][len];  //b的最高得分
         
        for(int j=0;j<len;j++){
            for(int i=j;i>=0;i--){
                if(i==j){
                    a[i][j]= arr[i];
                    b[i][j]=0;
                }
                else{
                    a[i][j]= max(arr[i]+b[i+1][j],arr[j]+b[i][j-1]);
                    b[i][j]= arr[i]+a[i+1][j]+b[i+1][j]-a[i][j];
                     
                }
                 
            }
        }
         
        return max(a[0][len-1], b[0][len-1]);
 
    }
     
     
    int max(int a, int b){
        return a>b? a:b;
    }
};

发表于 2015-05-21 20:53:06 回复(3)
int WonerPerson(int arr[],int n)//比较函数,返回胜者的分数
{
    int i=0,j=n-1;
    int sum1=0,sum2=0;//定义甲(sum1),乙(sum2)的计数器
    while(i<=j){
        if(arr[i]>=arr[j]) {sum1+=arr[i];i++;}
        else {sum1+=arr[j];j--;}

        if(arr[i]>=arr[j]) {sum2+=arr[i];i++;}
        else {sum2+=arr[j];j--;}
    }
    return sum1>sum2?sum1:sum2;
}
int main (void)
{
    int n=4;
    int arr[]={7,6,5,9};
    int king=WonerPerson(arr,n);
    printf("%5d",king);
    system("pause");
    return 0;
}

发表于 2015-03-28 15:12:03 回复(0)
buhui
发表于 2015-03-21 21:50:52 回复(0)
当arr=[8,7,5,3]时,为什么不是玩家1先拿8,玩家2拿3,玩家1拿7,玩家2拿5的顺序?这样最终玩家1获胜,分数为15,而不是13。
发表于 2015-03-17 21:29:24 回复(1)
var arr = [8,7,5,3], person = [0,0];
while (arr.length) {
    person[arr.length%2] += arr[0] > arr[arr.length - 1] ? arr.shift() : arr.pop();
}
发表于 2015-03-12 21:50:46 回复(0)