常见动态规划模型【最大子段和、LIS、LCS】

最大子段和

例1:下面数列的最大子段和是多少
-2,11,-4,13,-5,-2

概念:给定一个由数字组成的序列,其中连续的一段子序列称为一个子段,子段中的所有数之和称为子段和,这里只考虑非空子段,即至少包含一个元素的子段。

暴力方法

  • 1.最暴力的算法,就是枚举两个端点,遍历所选出的子段求和。枚举端点复杂度为 O ( n 2 ) O(n^2) O(n2),求一个子段的和,复杂度为 O ( n ) O(n) O(n),因此时间复杂度为 O ( n 3 ) O(n^3) O(n3)
  • 2.求一个子段和可以预处理前缀和进行优化,将这一部分复杂度将为 O ( 1 ) O(1) O(1),总时间复杂度降为 O ( n 2 ) O(n^2) O(n2)

动态规划算法

分析

  • 对于全是非正数的序列,很明显结果就是其中元素的最大值
  • 对于有正数的序列,考虑以每一个点为结尾的最大子段和,这个子段一定满足其前缀和为非负,因为如果有一个前缀是负的,那么减掉这个前缀对于这个点一定更优,并且这个子段要尽量向前延伸

实现

  • 所以我们可以使用一次扫描,记录目前统计的 s u m sum sum及答案 a n s ans ans。当 s u m sum sum加上当前位置数如果还是正数就继续累加 s u m sum sum,否则将 s u m sum sum置为0.这样可以舍去所有前缀为负数的情况,并且保证这个子段尽可能长了,每一次 s u m sum sum如果比 a n s ans ans大的话就更新 a n s ans ans,这样就得到了最大子段和
  • 时间复杂度为 O ( N ) O(N) O(N)

完整实现

#include <iostream>
#include <algorithm>
using namespace std;
const int inf = 0x7fffffff;
int num[10];
int main() {
	int n;
	cin >> n;
	for (int i = 0; i < n; ++i) {
		cin >> num[i];
	}
	int ans = -inf;
	//先标记ans为num数组中最大值
	for (int i = 0; i < n; ++i) {
		ans = max(ans, num[i]);
	}
	if (ans <= 0) { //最大值小于0则输出
		cout << ans << endl;
	} else {
		int sum = 0;
		for (int i = 0; i < n; ++i) {
			//前缀和为负,将sum置为0
			if (sum + num[i] < 0) {
				sum = 0;
			} else {  //否则继续累加
				sum += num[i];
			}
			ans = max(ans, sum);
		}
	}
	cout << ans << endl;
	return 0;
}

i n p u t input input:

6
-2 11 -4 13 -5 -2

o u t p u t output output:

20

最长上升子序列(LIS)

例2:在序列5,2,7,9,4,5,7,10中,最大上升子序列的长度为

概念:在原序列取任意项,不改变他们在原来数列的先后次序,得到的序列称为原序列的子序列。最长上升子序列,就是给定序列中一个最长的、数值从低到高排列的子排列,最长上升子序列不一定是唯一的。例如:序列2,1,5,3,6,4,6,3的最长上升子序列为1,3,4,6和2,3,4,6,长度均为4

分析

  • 先确定动态规划的状态,这个问题可以用序列某一项作为结尾来作为一个状态。用 d p [ i ] dp[i] dp[i]表示一定以第 i i i项结尾的最长上升子序列。用 a [ i ] a[i] a[i]表示第 i i i项的值,如果有 j &lt; i j &lt; i j<i a [ j ] &lt; a [ i ] a[j] &lt; a[i] a[j]<a[i],那么把第i项接到第j项后面构成的子序列长度为 d p [ i ] = d p [ j ] + 1 dp[i] = dp[j] + 1 dp[i]=dp[j]+1
  • 要使 d p [ i ] dp[i] dp[i]为以 i i i结尾的最长上升子序列,需要枚举所有满足条件的 j j j。所以状态转移方程为:
	dp[i] = max(dp[i], dp[j] + 1), 1 <= j < i && a[j] < a[i]
  • 最后, d p dp dp数组中的最大值就是最大上升子序列的长度了
  • 时间复杂度为 O ( n 2 ) O(n^2) O(n2)

根据上述状态转移方程可以得到下表:

i12345678
a [ i ] a[i] a[i]527945710
d p [ i ] dp[i] dp[i]11232345

完整实现

#include <iostream>
using namespace std;
int dp[101], a[101], n;
int LIS() {
	int ans = 0;
	for (int i = 1; i <= n; ++i) {
		dp[i] = 1;
		for (int j = 1; j < i; ++j) {
			if (a[j] < a[i]) {
				dp[i] = max(dp[i], dp[j] + 1);
			}
		}
		ans = max(ans, dp[i]);  //ans记录当前位置的最大上升子序列
	}
	return ans;
}
int main() {
	cin >> n;
	for (int i = 1; i <= n; ++i) {
		cin >> a[i];
	}
	cout << LIS() << endl;
	return 0;
}

i n p u t input input:

8
5 2 7 9 4 5 7 10

o u t p u t output output:

5

最长公共子序列(LCS)

最长公共子序列:给定两个序列 S 1 S_1 S1 S 2 S_2 S2,求两者的公共子序列 S 3 S_3 S3的最长的长度

分析

  • 有了前面的基础,可以发现这个问题仍然可以按照序列的长度来划分状态,也就是 S 1 S_1 S1的前 i i i个字符和 S 2 S_2 S2的前 j j j个字符的最长公共子序列长度,记为 l c s [ i ] [ j ] lcs[i][j] lcs[i][j]
  • 如果 S 1 S_1 S1的第i项,和 S 2 S_2 S2的第 j j j项相等,那么 S 1 [ i ] S_1[i] S1[i] S 2 [ j ] S_2[j] S2[j]作为公共子序列的末尾,则:
	lcs[i][j] = lcs[i - 1][j - 1] + 1
  • 也可以不让 S 1 [ i ] S_1[i] S1[i] S i [ j ] S_i[j] Si[j]作为公共子序列的末尾,则:
	lcs[i][j] = max(lcs[i][j - 1], lcs[i - 1][j])

转移方程: l c s [ i ] [ j ] = { l c s [ i − 1 ] [ j − 1 ] + 1 S 1 [ i ] = S 2 [ j ] max ⁡ { l c s [ i ] [ j − 1 ] , l c s [ i − 1 ] [ j ] } S 1 [ i ] ≠ S 2 [ j ] lcs[i][j] = \begin{cases} lcs[i - 1][j - 1] + 1&amp; S_1[i] = S_2[j] \\ \max\{ lcs[i][j - 1], lcs[i - 1][j]\} &amp; S_1[i] \neq S_2[j] \end{cases} lcs[i][j]={lcs[i1][j1]+1max{lcs[i][j1],lcs[i1][j]}S1[i]=S2[j]S1[i]̸=S2[j]

举个例子,两个序列 S 1 S_1 S1 = a b c f b c abcfbc abcfbc, S 2 S_2 S2 = a b f c a b abfcab abfcab,根据状态转移方程可得下表:

l c s lcs lcs01( a a a)2( b b b)3( c c c)4( f f f)5( b b b)6( c c c)
00000000
1( a a a)0111111
2( b b b)0122222
3( f f f)0122333
4( c c c)0123334
5( a a a)0123334
6( b b b)0123344

完整实现:

#include <iostream>
#include <cstring>
#include <string>
using namespace std;
int dp[101][101];
int main() {
	string a, b;
	cin >> a >> b;
	int lena = a.size();
	int lenb = b.size();
	for (int i = 1; i <= lena; ++i) {
		for (int j = 1; j <= lenb; ++j) {
			if (a[i - 1] == b[j - 1]) {
				dp[i][j] = dp[i - 1][j - 1] + 1;
			} else {
				dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
			}
		}
	}
	cout << dp[lena][lenb] << endl;
	return 0;
}

i n p u t input input:

abcdefgh
acjlfabhh

o u t p u t output output:

4

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值