研究生复试上机基本题型及思路总结(动态规划)

目录

 

一、动态规划问题

1.1 递推求解

1.2 最大连续子序列和

1.3 最长递增(递减)子序列(和)

1.4 最长公共子序列

1.5 背包问题


一、动态规划问题

动态规划通常用来解决最优解问题,原理同样是把一个大问题分解为若干个小问题,但是如果使用传统的分治法会重复计算底层小问题,所以可以将小问题的结果保存下来,在需要时可以直接取得,不需要重复计算,提高效率。

1.1 递推求解

递推求解的典型应用是求斐波那契数列,这类问题可以简单概括为第 n 个状态可以由 n - 1以及之前的状态获得,只要找到初始状态以及递推关系即可。

e.g. 1. 斐波那契数列求解 2. N阶楼梯上楼问题 3. 吃糖果问题

1.2 最大连续子序列和

此类问题是求给定一个序列,求所有连续子序列中和最大的子序列的和,此类问题求解的关键是设置一个数组 dp ,dp[i] 表示以 a[i] (a 为初始序列)为结尾的序列的最大子序列和,所以状态转移方程就是 dp[i] = max(a[i] , dp[i - 1] + a[i])。

该问题还可以扩展为二维情况,即求给定矩阵的和最大的子矩阵。解决方法是将二维问题转化为一维问题,计算矩阵中任意两行对应元素的累加,然后采用求最大连续子序列和的方法获得结果。

e.g. 1. 最大序列和 清华大学 浙江大学 2. 最大子矩阵 

1.3 最长递增(递减)子序列(和)

此类问题是求解给定一个序列,求该序列中最长递增(递减)的子序列的长度或者子序列的和,该子序列不一定连续。 求解此类问题的关键仍是设置一个 dp 数组用来存储截止到 a[i] 的最长递增子数列的长度,令 dp[i] 的初始值为 1 ,代码如下:

for(int i = 0;i < n;++i){
    dp[i] = 1;
    for(int j = 0;j < i;++j){
        if(a[i] > a[j]){
            dp[i] = max(dp[i],dp[j] + 1);
        }
    }
}

此问题还可以转化为最长递增(递减)子序列和,代码只需要做少量修改如下:

for(int i = 0;i < n;++i){
    dp[i] = a[i];
    for(int j = 0;j < i;++j){
        if(a[i] > a[j]){
            dp[i] = max(dp[i],dp[j] + a[i]);
        }
    }
}

e.g. 1. 拦截导弹 2. 最大上升子序列和

1.4 最长公共子序列

此类问题求解两个字符串中最长的公共子序列,这次使用二维数组 dp[i][j] 用来记录截止到第一个序列的第 i 位和第二个序列的第 j 位最长公共子序列的长度。如果 a[i] 和 b[j] 相等时,那么 dp[i][j] = dp[i - 1][j - 1] + 1,如果不等的话 dp[i][j] = max(dp[i - 1][j] , dp[i][j - 1]),代码如下:

scanf("%s%s",s1 + 1,s2 + 1);   // 从下标 1 开始读入,可以用二维数组中的 0 行 0 列做初始情况。 
int n = strlen(s1 + 1);
int m = strlen(s2 + 1);
for(int i = 0;i <= n;++i){
	for(int j = 0;j <= m;++j){
		if(i == 0 || j == 0){
			dp[i][j] = 0;
		}
		if(s1[i] == s2[j]){
			dp[i][j] = dp[i - 1][j - 1] + 1;
		}else{
			dp[i][j] = max(dp[i- 1][j],dp[i][j - 1]);
		}
	}
}

e.g. 1. Coincidence

1.5 背包问题

背包问题是动态规划中一个典型的问题,具体又可以分为 0-1 背包、完全背包、多重背包问题。

1.5.1 0-1 背包

0 - 1 背包问题是每一个物品只有一个,也就是只能选一次,是最基本的背包问题,用 dp [i] 表示背包容量为 i 时的最大价值,代码思路是对于每一个物体,从 j 为 m (最大背包容量)开始,到 j 为当前物体体积,dp[j] = max(dp[j],dp[j - m[i]] + v[i]);代码如下:

for(int i = 0;i <= m;++i){
	dp[i] = 0;
}
for(int i = 0;i < n;++i){   // n 为物体的数目。 
	for(int j = m;j >= w[i];--j){     // 倒序的方法为  0 - 1 背包特征。
		dp[j] = max(dp[j],dp[j - w[i]] + v[i]);   // w 为重量,v 为价值。 
	} 
}

e.g. 1. 点菜问题 2. 采药 3. 最小邮票数

1.5.2 完全背包

完全背包与 0 - 1背包最大的不同是完全背包问题每个物体有无限多个,没有数量的限制可以随便选,只要将 0 - 1背包问题的 j 的遍历逆向,就可以把 0 - 1背包转化为完全背包。代码如下:

for(int i = 0;i <= m;++i){
	dp[i] = 0;
}                                                 // 初始化 dp。
for(int i = 0;i < n;++i){                         // n 为物体的数目。 
	for(int j = w[i];j <= m;++j){                 // 正序的方法为完全背包特征。
		dp[j] = max(dp[j],dp[j - w[i]] + v[i]);   // w 为重量,v 为价值。 
	} 
}

此外,如果要求背包正好装满,初始化 dp 的时候,除了dp [0] 为 0 之外,其他的均初始化为最大值:

dp[0] = 0;
for(int i = 1;i <= m;++i){
    dp[i] = INT_MAX;
}

// 下同。

1.5.3 多重背包

多重背包和完全背包问题的最大区别是每一种物体的数量不再是无限个,而是有限个,采用的解决办法是将多个同类物体分解合并,把多重背包转化为 0 - 1背包。

// n 种物品,m容量的背包,v[] 存价值,m[] 存质量,k[] 存数目。
// value[] 存分解之后的价值, weight[] 存分解之后的质量。

int number = 0;  //分解之后总的物体的数目
for(int i = 0;i < n;++i){
	scanf("%d%d%d",&w[i],&v[i],&k[i]); 
	for(int j = 1;j <= k[i];j *= 2){
		value[num] = v[i] * j;
		weight[num] = w[i] * j;
		k[i] -= j;
		number++;
	}
	if(k[i] > 0){
		value[num] = k[i] * v[i];
		weight[num] = k[i] *  w[i];
		number++;
	}
}
for(int i = 0;i <= m;++i){
	dp[i] = 0;                //不要求正好装满。 
}	

for(int i = 0;i < number;++i){         // 0 - 1背包问题。 
	for(int j = m;j >= weight[i];--j){
		dp[j] = max(dp[j],dp[j - weight[i]] + value[i]);
	}
} 

 

 

 

至此动态规划经典问题结束,动归问题仍有很多变形,需要多加练习加强掌握!

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值