纸牌博弈问题
2015-08-05 22:40
330 查看
题目:有一个整型数组A,代表数值不同的纸牌排成一条线。玩家a和玩家b依次拿走每张纸牌,规定玩家a先拿,玩家b后拿,但是每个玩家每次只能拿走最左或最右的纸牌,玩家a和玩家b都绝顶聪明,他们总会采用最优策略。请返回最后获胜者的分数。给定纸牌序列A及序列的大小n,请返回最后分数较高者得分数(相同则返回任意一个分数)。
测试样例:
[1,2,100,4],4
返回:101
解析:
a和b都是绝顶聪明,他们每次拿元素时,肯定是按对自己最有力的方式拿。该题目先由最普通的递归解法,然后进行优化,到动态规划。
递归方式,对数组arr,元素数为n。
F(arr, l , r)表示对于数组arr,元素从l到r,先拿可以达到的最大分数;
S(arr, l, r)表示对于数组arr, 元素从l到r,后拿可以达到的最大分数。
对于F(arr, l, r),先拿时,有两种拿法,拿第一个arr[l],或最后一个arr[r];如果拿arr[l],那么剩余的arr[l+1,....r]能拿到的最大分数为S(arr, l+1, r),分数为arr[l] +S(arr, l+1, r); 如果拿arr[r],剩余的arr[l, ...r-1]能拿到的最大分数为S(arr, l, r-1),分数为arr[r] + S(arr, l, r-1),因为对于先拿后剩余的数组,当前人再拿的话是后拿的,然后取这两种拿法较大的分数。
对于S(arr, l, r),如果前一个人先拿了arr[l],则后拿的分数为F(arr, l+1, r),如果前一个人先拿了arr[r],则后拿的分数为F(arr, l, r-1),因为对于剩余的元素来说,你是先拿的,取两种方式的较小值才是S的值。(为什么取较小值,而不是较大值?因为a和b都是绝顶聪明人,你是在另一个绝顶聪明人之后才拿的,他给你剩下的肯定是较坏的情况)
递归实现的方式如下,可以再进行动态规划方式的优化,接下来再讲。
态规划定义了两个表F和S,F[i][j]表示arr[i...j]先拿的最大分数,S[i][j]表示arr[i...j]后拿的最大分数。最终比较F[0][n-1]和S[0][n-1]的值,返回较大的即可。
在遍历填写两个表之前,我们可以对表进行初始化。初始化后,可以按列进行遍历,行从最后一个开始,因为每个F[i][j]和S[i][j]的值都是由F[i+1][j]、F[i][j-1]或S[i+1][j]、S[i][j-1]得到的。
动态规划的代码如下:
测试样例:
[1,2,100,4],4
返回:101
解析:
a和b都是绝顶聪明,他们每次拿元素时,肯定是按对自己最有力的方式拿。该题目先由最普通的递归解法,然后进行优化,到动态规划。
递归方式,对数组arr,元素数为n。
F(arr, l , r)表示对于数组arr,元素从l到r,先拿可以达到的最大分数;
S(arr, l, r)表示对于数组arr, 元素从l到r,后拿可以达到的最大分数。
对于F(arr, l, r),先拿时,有两种拿法,拿第一个arr[l],或最后一个arr[r];如果拿arr[l],那么剩余的arr[l+1,....r]能拿到的最大分数为S(arr, l+1, r),分数为arr[l] +S(arr, l+1, r); 如果拿arr[r],剩余的arr[l, ...r-1]能拿到的最大分数为S(arr, l, r-1),分数为arr[r] + S(arr, l, r-1),因为对于先拿后剩余的数组,当前人再拿的话是后拿的,然后取这两种拿法较大的分数。
对于S(arr, l, r),如果前一个人先拿了arr[l],则后拿的分数为F(arr, l+1, r),如果前一个人先拿了arr[r],则后拿的分数为F(arr, l, r-1),因为对于剩余的元素来说,你是先拿的,取两种方式的较小值才是S的值。(为什么取较小值,而不是较大值?因为a和b都是绝顶聪明人,你是在另一个绝顶聪明人之后才拿的,他给你剩下的肯定是较坏的情况)
递归实现的方式如下,可以再进行动态规划方式的优化,接下来再讲。
/* 返回较大值 */ int Max(int a, int b) { return ((a > b) ? a : b); } /* 返回较小值 */ int Min(int a, int b) { return ((a < b) ? a : b); } /* 对数组arr,从l到r元素,先拿的最大分数 */ int F(int arr[], int l, int r) { if (l == r) { return arr[l]; } return Max(arr[l] + S(arr, l+1, r), arr[r] + S(arr, l, r-1)); } /* 对数组arr,从l到r元素,后拿的最大分数 */ int S(int arr[], int l, int r) { if (l == r) { return 0; } return Min(F(arr, l+1, r), F(arr, l, r-1)); } int FindWinnerScore(int arr[], int l, int r) { int A_score = 0; int B_score = 0; A_score = F(arr, l, r); B_score = S(arr, l, r); return ((A_score > B_score) ? A_score : B_score); }
态规划定义了两个表F和S,F[i][j]表示arr[i...j]先拿的最大分数,S[i][j]表示arr[i...j]后拿的最大分数。最终比较F[0][n-1]和S[0][n-1]的值,返回较大的即可。
在遍历填写两个表之前,我们可以对表进行初始化。初始化后,可以按列进行遍历,行从最后一个开始,因为每个F[i][j]和S[i][j]的值都是由F[i+1][j]、F[i][j-1]或S[i+1][j]、S[i][j-1]得到的。
动态规划的代码如下:
int DP(int arr[], int n) { int i = 0; int j = 0; int** F = NULL; int** S = NULL; F = (int**)malloc(sizeof(int*)*n); S = (int**)malloc(sizeof(int*)*n); for (i = 0; i < n; i++) { F[i] = (int*)malloc(sizeof(int)*n); S[i] = (int*)malloc(sizeof(int)*n); } /* 初始化 */ F[0][0] = arr[0]; S[0][0] = 0; F[n-1][n-1] = arr[n-1]; S[n-1][n-1] = 0; for (i = 1; i < n; i++) { F[i][0] = 0; S[i][0] = 0; } for (i = 0; i < n-1; i++) { F[n-1][i] = 0; S[n-1][i] = 0; } for (j = 1; j < n; j++) { for (i = n-2; i >= 0; i--) { if (i <= j) { F[i][j] = Max(arr[i] + S[i+1][j], arr[j] + S[i][j-1]); S[i][j] = Min(F[i+1][j], F[i][j-1]); } else { F[i][j] = 0; S[i][j] = 0; } } } return Max(F[0][n-1], S[0][n-1]); }
相关文章推荐
- C#中的递归APS和CPS模式详解
- WinForm实现按名称递归查找控件的方法
- C#中的尾递归与Continuation详解
- C++动态规划之最长公子序列实例
- C++动态规划之背包问题解决方法
- C#递归实现显示文件夹及所有文件并计算其大小的方法
- php递归创建目录的方法
- Javascript递归打印Document层次关系实例分析
- oracle 使用递归的性能提示测试对比
- 使用curl递归下载软件脚本分享
- Perl脚本实现递归遍历目录下的文件
- JavaScript的递归之递归与循环示例介绍
- C# 递归查找树状目录实现方法
- 全排列算法的非递归实现与递归实现的方法(C++)
- php递归列出所有文件和目录的代码
- java递归菜单树转换成pojo对象
- 一个JavaScript递归实现反转数组字符串的实例
- Java中的递归详解(用递归实现99乘法表来讲解)
- C语言的递归思想实例分析
- php通过递归方式复制目录和子目录的方法