1.题目:预测赢家
给定一个表示分数的非负整数数组。玩家1从数组任意一端拿取一个分数,随后玩家2继续从剩余数组任意一端拿取分数,然后玩家1拿,……。每次一个玩家只能拿取一个分数,分数被拿取之后不再可取。直到没有剩余分数可取时游戏结束。最终获得分数总和最多的玩家获胜。
给定一个表示分数的数组,预测玩家1是否会成为赢家。你可以假设每个玩家的玩法都会使他的分数最大化。
2.算法:
1.递归
2.思想优化的递归
3.DP 动态规划
3.算法思想:(解释不通: 看代码!!!)
1、递归
每次从头和尾各选一个,然后接着取下一个状态的小的那一个,因为对方肯定把大的取走了,然后最后的结果取最大,所以这题也是最大的最小,max(min()),不同于猜数字大小 II,这题是最好的最坏,是min(max())
2、动态规划(自底向上)
动态规划的方法是比较巧妙
4.动态规划的思想:
动态规划(Dynamic programming)是一种在数学、计算机科学和经济学中使用的,通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。 动态规划常常适用于有重叠子问题和最优子结构性质的问题,动态规划方法所耗时间往往远少于朴素解法。 动态规划背后的基本思想非常简单。大致上,若要解一个给定问题,我们需要解其不同部分(即子问题),再合并子问题的解以得出原问题的解。 通常许多子问题非常相似,为此动态规划法试图仅仅解决每个子问题一次,从而减少计算量: 一旦某个给定子问题的解已经算出,则将其记忆化存储,以便下次需要同一个子问题解之时直接查表。 这种做法在重复子问题的数目关于输入的规模呈指数增长时特别有用。 关于动态规划最经典的问题当属背包问题。
性质:
- 最优子结构性质。如果问题的最优解所包含的子问题的解也是最优的,我们就称该问题具有最优子结构性质(即满足最优化原理)。最优子结构性质为动态规划算法解决问题提供了重要线索。
- 子问题重叠性质。子问题重叠性质是指在用递归算法自顶向下对问题进行求解时,每次产生的子问题并不总是新问题,有些子问题会被重复计算多次。动态规划算法正是利用了这种子问题的重叠性质,对每一个子问题只计算一次,然后将其计算结果保存在一个表格中,当再次需要计算已经计算过的子问题时,只是在表格中简单地查看一下结果,从而获得较高的效率。
- 无后效性:即某阶段状态一旦确定,就不受这个状态以后决策的影响。也就是说,某状态以后的过程不会影响以前的状态,只与当前状态有关。
步骤:
- 划分:按照问题的特征,把问题分为若干阶段。注意:划分后的阶段一定是有序的或者可排序的
- 确定状态和状态变量:将问题发展到各个阶段时所处的各种不同的客观情况表现出来。状态的选择要满足无后续性
- 确定决策并写出状态转移方程:状态转移就是根据上一阶段的决策和状态来导出本阶段的状态。根据相邻两个阶段状态之间的联系来确定决策方法和状态转移方程
- 边界条件:状态转移方程是一个递推式,因此需要找到递推终止的条件
代码:
/*************************************************
作者:She001
时间:2022/9/4
题目:预测赢家
给定一个表示分数的非负整数数组。玩家1从数组任意一端拿取一个分数,随后玩家2继续从剩余数组任意一端拿取分数,然后玩家1拿,……。每次一个玩家只能拿取一个分数,分数被拿取之后不再可取。直到没有剩余分数可取时游戏结束。最终获得分数总和最多的玩家获胜。
给定一个表示分数的数组,预测玩家1是否会成为赢家。你可以假设每个玩家的玩法都会使他的分数最大化。
算法:
1.递归
2.算法优化递归
3.dp算法
***************************************************/
#include<bits/stdc++.h>
using namespace std;
//
//算法递归
int min(int a,int b)//选择最小的数返回
{
if(a>b)
{
return b;
}
else
{
return a;
}
}
int max(int a,int b)//选择最大的数返回
{
if(a>b)
{
return a;
}
else
{
return b;
}
}
int fangfa_1(int num[],int n,int l,int r)//num 数值的数组 n 数组元素的数量 l 左边节点的位置 , r 右边节点的位置
{
if(l==r)//当这个范围只剩下一个数值的时候直接返回
{
return num[l];
}
int left =0;
int right =0;
if((r-l )==1)//这个范围只剩下两个数值
{
left = num[l];//选择左边 返回数组里面 r 位置的数值
right = num[r];// 选择右边 返回数组里面 l 位置的数值
}
if((r-l)>1)//有多个数值的情况
{
// 下面这个语句的意思是 ,num[l] 我假如在这个 数组的左边是 l 右边 是 r 的情况下, 我选择最左边的值,
// +号 因为是递归所以需要累加,把上一次的值加上
// min() 的意思是 我们两个人都是聪明人 所以只会留给对方的挑选范围是只能挑选出最小值的(相对于别的挑选方式)。
// 是相对于我选择之后 另外一个人的选择之后 给我的是只能选择出最小的值 的范围
//
left = num[l] + min(fangfa_1(num,n,l+2,r),fangfa_1(num,n,l+1,r-1));
right= num[r] + min(fangfa_1(num,n,l+1,r-1),fangfa_1(num,n,l,r-2));
}
return max(left,right);//我要返回我的挑选方式里面最大的值
}
//小的改动
//相比上面的时间减少
int fangfa_1_1(int num[],int n,int l,int r)//num 数值的数组 n 数组元素的数量 l 左边节点的位置 , r 右边节点的位置
{
if(l==r)//当这个范围只剩下一个数值的时候直接返回
{
return num[l];
}
int left =0;
int right =0;
if((r-l )==1)//这个范围只剩下两个数值
{
left = num[l];//选择左边 返回数组里面 r 位置的数值
right = num[r];// 选择右边 返回数组里面 l 位置的数值
}
if((r-l)>1)//有多个数值的情况
{
// 下面这个语句的意思是 ,num[l] 我假如在这个 数组的左边是 l 右边 是 r 的情况下, 我选择最左边的值,
// +号 因为是递归所以需要累加,把上一次的值加上
// min() 的意思是 我们两个人都是聪明人 所以只会留给对方的挑选范围是只能挑选出最小值的(相对于别的挑选方式)。
// 是相对于我选择之后 另外一个人的选择之后 给我的是只能选择出最小的值 的范围
//
int gg= fangfa_1(num,n,l+1,r-1);//减少递归次数
left = num[l] + min(fangfa_1(num,n,l+2,r),gg);
right= num[r] + min(gg,fangfa_1(num,n,l,r-2));
}
return max(left,right);//我要返回我的挑选方式里面最大的值
}
//思想优化的递归
//这个思想是 我选择的数减去 你选择的数, 其他的差不多
//简单举例:
//数组 = {1,2,3 }
//开始我选择有两种方法 选择左边 1 , 与选择右边 3
//所以 left = 1 - fangfa_2( 2,3 ) = 1- (2 - fangfa_2(3) = 1- (2-3 )
// = 1- (3 - fangfa2(2) = 1- (3-2 )=0 最优的过程 返回的是这个
// right = 3- fangfa_2(1,2) =3 - (2 - fangfa(1)) = 3-(2-1)=2; //实际上 运行的是这个
// = 3-(1- fangfa_2(2))= 3(1-2);
int fangfa_2(int num[],int n,int l,int r)//num 数值的数组 n 数组元素的数量 l 左边节点的位置 , r 右边节点的位置
{
if(l==r)//只有一个数值的时候 返回这个值, 因为这个递归是减 1 所以只有这种预防就行了
{
return num[l];
}
//下面这句话的意思:
// num[] 我选择 数组此时 最左边的数值 ,
// 减 fangfa_2 意思是 减去对手的最优的选择;
int left= num[l]-fangfa_2(num,n,l+1,r);
int right=num[r]-fangfa_2(num,n,l,r-1);
return max(left,right);//在两种方法里面选择, 最好的方法,返回
}
///
//算法:动态规划 dp算法
//dp算法的思路 就是,把数据存储到一个存在的空间方便查询,节省时间
int fangfa_3(int num[],const int n)//num 数组 , int n 数组里面元素的个数
{
int dp[n][n]={0};//建立二维数组
for(int i=0;i<n;i++)//初始化数据
{
dp[i][i]=num[i];//相当于之前的 if(r==l )return num[i] ,, 因为这里是二维数组存储的是数据
}
for(int i=n-2;i>=0;i--)//因为数组的最右边 等于 n-1 所以 我们的数据 从哪里开始? 从第一个挑选开始,对手挑选最后一个,我挑选倒数第二个
{
for(int j=i+1;j<n;j++)
{
//解释语句
//首先 dp[i][j] 代表的意思是, 在数组 最左边是 i 最右边是 j 的这个范围 里面,dp[i][j] 代表者 这个差值的最优解
//为啥 因为,我们每个范围都有遍历啊 , 比如说
//举例说明: 数组 {1,2,3}
//dp[1][2];
//代表 {2 , 3} 的数组里面的最优解的差值 dp[1][2] == max(2 - 3, 3-2)=1;
//dp [0][2] 代表着 dp[0][2] = max(1-dp[1][2],3-dp[0][1]) 这个和之前一样 有两种方法 一个选择左边的值, 一个选择右边的值,从两个值里面挑选最好的值。
dp[i][j]=max(num[i]-dp[i+1][j],num[j]-dp[i][j-1]);//在这里i 是代表左边, j 是代表右边
}
}
return dp[0][n-1];//所以我们需要返回的最优解在 最左边是 0 ,最右边的 n- 1 数组的最优解。
}
int main()
{
int sum1=0;
int sum2=0;
int num1[]={1,2,3,4,5,6};
int num2[]={1,200,2,3,6};
for(int i=0;i<6;i++)
{
sum1+=num1[i];
}
for(int i=0;i<5;i++)
{
sum2+=num2[i];
}
int a1=fangfa_1(num1,6,0,5);
int a2=fangfa_1(num2,5,0,4);
if(a1>(sum1-a1))//当我们返回的最大值 的两倍大于数组的总的和的时候,我们有必赢的机会
{
cout<<"a1 = "<<"true"<<endl;
}
else
{
cout<<"a1 = "<<"false"<<endl;
}
if(a2>(sum1-a2))//当我们返回的最大值 的两倍大于数组的总的和的时候,我们有必赢的机会
{
cout<<"a2 = "<<"true"<<endl;
}
else
{
cout<<"a2 = "<<"false"<<endl;
}
///
int b1=fangfa_1_1(num1,6,0,5);
int b2=fangfa_1_1(num2,5,0,4);
if(b1>(sum1-b1))//当我们返回的最大值 的两倍大于数组的总的和的时候,我们有必赢的机会
{
cout<<"b1 = "<<"true"<<endl;
}
else
{
cout<<"a1 = "<<"false"<<endl;
}
if(b2>(sum1-b2))//当我们返回的最大值 的两倍大于数组的总的和的时候,我们有必赢的机会
{
cout<<"b2 = "<<"true"<<endl;
}
else
{
cout<<"b2 = "<<"false"<<endl;
}
/
int c1=fangfa_2(num1,6,0,5);
int c2=fangfa_2(num2,5,0,4);
if(c1>0)//fangfa_2 返回的是我减去对手数值的差 的累加和
{
cout<<"c1 = "<<"true"<<endl;
}
else
{
cout<<"c1 = "<<"false"<<endl;
}
if(c2>0)//fangfa_2 返回的是我减去对手数值的差 的累加和
{
cout<<"c2 = "<<"true"<<endl;
}
else
{
cout<<"c2 = "<<"false"<<endl;
}
//
int d1=fangfa_3(num1,6);
int d2=fangfa_3(num2,5);
if(d1>0)//fangfa_2 返回的是我减去对手数值的差 的累加和
{
cout<<"d1 = "<<"true"<<endl;
}
else
{
cout<<"d1 = "<<"false"<<endl;
}
if(d2>0)//fangfa_2 返回的是我减去对手数值的差 的累加和
{
cout<<"d2 = "<<"true"<<endl;
}
else
{
cout<<"d2 = "<<"false"<<endl;
}
return 0;
}