一个面试题从暴力递归到动态规划

题目

有一排正数,玩家A和玩家B都可以看到。
每位玩家在拿走数字的时候,都只能从最左和最右的数中选择一个。
玩家A先拿,玩家B再拿,两人交替拿走所有的数字,
两人都力争自己拿到的数的总和比对方多。请返回最后获胜者的分数。

例如:
5,2,3,4
玩家A先拿,当前他只能拿走5或者4。
如果玩家A拿走5,那么剩下2,3,4。轮到玩家B,此时玩家B可以选择2或4中的一个,…
如果玩家A拿走4,那么剩下5,2,3。轮到玩家B,此时玩家B可以选择5或3中的一个,…


思路:

首先需要理解这个“两人力争自己拿到的数的总和比对方多”的含义,它说明两人选择数的时候不是只是瞅着当前数组两侧的最大的数,而是需要长远的考虑,即我可以不拿当前最大的数,但我最终的和要比你的大,这就是这句话的含义。
所以我首先能想出来的代码如下:

class Solution
{
public:
	int static Max_mark(int a[], int len)
	{
		if (a == nullptr || len == 0)
		{
			return 0;
		}
		return max(before_get(a,0,len-1),after_get(a,0,len-1));
	}

	int static before_get(int a[], int i, int j) //先手取大的
	{
		if (i == j)
		{
			return a[i];
		}
		else
		{
			return max(a[i] + after_get(a, i + 1, j), a[j] + after_get(a, i, j - 1));
		}
	}

	int static after_get(int a[], int i, int j) //后手的只能取小的,也只去得到小的
	{
		if (i == j)
		{
			return 0;
		}
		else
		{
			return min(before_get(a, i + 1, j), before_get(a, i, j - 1));
		}
	}
};

优化:

上面那种方法使用的是暴力递归的思路。
我们分析一下:
在这里插入图片描述
如图:我们假设有一个数组
int a[5] = {1,3,5,4,2};
(以下简称before_get为f函数,after_get为s函数)
所以以上面的方法来做和简化为:
return max(f(a,0,4),s(a,0,4));
因为a在整个程序运行的过程中是不变的,所以可以再次简化为(伪代码方便理解):
return max(f(0,4),s(0,4))
根据f函数的内容决定f(0,4)的时s(1,4)和s(0,3),决定s(1,4)的是f(2,4)和f(1,3)

		f(0,4)			
	s(1,4)	s(0,3)		
f(2,4)	f(1,3)	f(0,2)	

看到没有,s(1,4)和s(0,3)都依赖于f(1,3)。所以,如果单纯的使用递归的话会有大量的重复计算的问题,所以我们可以使用动态规划来优化时间复杂度。

static int Max_mark_2(int a[], const int len) 
	{
		if (a == nullptr || len == 0)
		{
			return 0;
		}
		int** f = new int* [len];
		int** s = new int* [len];
		for (int i = 0; i < len; i++)
		{
			f[i] = new int[len];
			s[i] = new int[len];
		}
		for (int i = 0; i < len; i++)
		{
			for (int j = 0; j < len; j++)
			{
				f[i][j] = 0;
				s[i][j] = 0;
			}
		}
		for (int j = 0; j < len; j++)
		{
			f[j][j] = a[j];
			for (int i = j-1; i>=0 ; i--)
			{
				f[i][j] = max(a[i] + s[i + 1][j], a[j] + s[i][j - 1]);
				s[i][j] = min(f[i][j - 1], f[i + 1][j]);
			}
		}
		return max(f[0][len - 1], s[0][len - 1]);
	}

核心步骤是:

		for (int j = 0; j < len; j++)
		{
			f[j][j] = a[j];
			for (int i = j-1; i>=0 ; i--)
			{
				f[i][j] = max(a[i] + s[i + 1][j], a[j] + s[i][j - 1]);
				s[i][j] = min(f[i][j - 1], f[i + 1][j]);
			}
		}

这是啥意思呢?我们分析一下,如图我们要得到的数据是f(0,4)和s(0,4),两者解的过程类似,单独以f(0,4)举例就行了,如上述代码,当j=0,循环只是给f[0][0]赋了个值,当j=1的时候,给f[0][1]和s[0][1]赋了值……当j=5时,赋值的循序为f[4][3]、f[4][2]、 f[4][1]、f[4][0],最终得到了我们所需要的f[4][0](和s[4][0])
这就是动态规划的思想。


当然上面的动态规划还可以继续优化,比如把s函数带入f函数,之后不需要讨论两张表f表和s表,只要讨论一张表,这是一个思路,读者可以自行思考。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值