动态规划 DP 01背包 搜索 记忆化搜索 优化 最长公共子序列问题

动态规划:DP(Dynamic Programming)。

最经典的DP问题:

01背包问题:

题意:有n个重量和价值分别为wi, vi的物品。从这些物品中挑选出总质量不超过W的物品,求所有挑选方案中价值总和的最大值。
输入:
n = 4
(w, v) = {(2, 3), (1, 2), (3, 4), (2, 2)}
W = 5
输出:
7(选择第0、1、3号物品)

这个经典问题,我们先使用最朴素的搜索试一下:

int n, W;
int w[MAX_N], v[MAX_N];

int rec(int i, int j)
{
	int res;
	if(i == n)
	{
		res = 0;
	}
	else if(j < w[i])
	{
		res = rec(i + 1, j);
	}
	else
	{
		res = max(rec(i + 1, j), rec(i + 1, j - w[i]) + v[i]);
	}
	return res;
}

void solve()
{
	printf("%d\n", rec(0, w));
}

这种方法的搜索深度是n,最坏时间复杂度就会O(2^n), 不算乐观,只好进行优化代码。

这里发现,代码搜索时候出现重复数据的计算,我们第一次优化的方向就很明确的,使用空间换取时间,开记录数组,使用记忆化搜索优化解题算法。

int dp[MAX_N + 1][MAX_W + 1];

int rec(int i, int j)
{
	if(dp[i][j] >= 0)
	{
		return dp[i][j];
	}
	int res;
	if(i == n)
	{
		res = 0;
	}
	else if(j < w[i])
	{
		res = rec(i + 1, j);
	}
	else
	{
		res = max(rec(i + 1, j), rec(i + 1, j - w[i]) + v[i]);
	}
	return dp[i][j] = res;
}

void solve()
{
	memset(dp, -1, sizeof(dp));
	printf("%d\n", rec(0, W));
}

由于参数的组合不过nW种, 所以时间复杂度仅为O(nW)。记忆化搜索让此解题代码的时间复杂度大大降低。

这里提一下memset, memset速度很快,可以在字节层次上对内存进行填充,-1的每一位二进制位都是1, 所以memset可以让高维数组初始化0或-1,其他数就不行了。

咱们看记忆化搜索的递推式:
dp[n][j] = 0
dp[i][j] = dp[i + 1][j] (j < w[i])
dp[i][j] = max(dp[i + 1][j], dp[i + 1][j - w[i]] + v[i])

简洁点,通过二次循环:

int dp[MAX_N + 1][MAX_W + 1];

void solve()
{
	for(int i=n-1;i>=0;i--)
	{
		for(int j=0;j<=W;j++)
		{
			if(j < w[i])
			{
				dp[i][j] = dp[i + 1][j];
			}
			else
			{
				dp[i][j]=max(dp[i + 1][j], dp[i + 1][j - w[i]] + v[i]);
			}
		}
	}
	printf("%d\n", dp[0][W]);
}

从记忆化搜索出发推导出递推式,也可以直接得到递推式,这种一步步按顺序求出问题的解的方法称为动态规划法,叫做DP,形式简介,时间复杂度是O(nW)。

这个DP中关于i的循环是逆向进行的,我们也可以改变一下递推关系,关于i的循环就能正向进行。
dp[0][j] = 0
dp[i + 1][j] = dp[i][j]
dp[i + 1][j] = max(dp[i][j - w[i]] + v[i])

void solve()
{
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<=W;j++)
		{
			if(j < w[i])
			{
				dp[i + 1][j] = dp[i][j];
			}
			else
			{
				dp[i + 1][j] = max(dp[i][j], dp[i][j - w[i]] + v[i]);
			}
		}
	}
	printf("%d\n", dp[n][W]);
}

好了,讨论完这些,我们来看一下另一个使用DP的经典问题:最长公共子序列问题(LCS)。

递推关系:
dp[i + 1][j + 1] = max(dp[i][j + 1], dp[i + 1][j]) 当:(s[i + 1] = t[j + 1])时
dp[i + 1][j + 1] = max(dp[i][j + 1], dp[i + 1][j]) (其他)

int n, m;
char s[MAX_N][MAX_M];

int dp[MAX_N + 1][MAX_M + 1];

void solve()
{
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<m;j++)
		{
			if(s[i] == t[j])
			{
				dp[i+1][j+1] = dp[i][j]+1;
			}
			else
			{
				dp[i+1][j+1]=max(dp[i][j+1], dp[i+1][j]);
			}
		}
	}
	printf("%d\n", dp[n][m]);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

三元湖有大锦鲤

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值