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

版权声明:本文为博主原创文章,转载请注明出处-- https://blog.csdn.net/qq_38790716/article/details/88076052

最大子段和

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

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

暴力方法

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

动态规划算法

分析

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

实现

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

6
-2 11 -4 13 -5 -2

o u t p u t 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] 表示一定以第 i i 项结尾的最长上升子序列。用 a [ i ] a[i] 表示第 i i 项的值,如果有 j &lt; i j &lt; i a [ j ] &lt; a [ i ] a[j] &lt; a[i] ,那么把第i项接到第j项后面构成的子序列长度为 d p [ i ] = d p [ j ] + 1 dp[i] = dp[j] + 1
  • 要使 d p [ i ] dp[i] 为以 i i 结尾的最长上升子序列,需要枚举所有满足条件的 j j 。所以状态转移方程为:
	dp[i] = max(dp[i], dp[j] + 1), 1 <= j < i && a[j] < a[i]
  • 最后, d p dp 数组中的最大值就是最大上升子序列的长度了
  • 时间复杂度为 O ( n 2 ) O(n^2)

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

i 1 2 3 4 5 6 7 8
a [ i ] a[i] 5 2 7 9 4 5 7 10
d p [ i ] dp[i] 1 1 2 3 2 3 4 5

完整实现

#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 :

8
5 2 7 9 4 5 7 10

o u t p u t output :

5

最长公共子序列(LCS)

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

分析

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

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

l c s lcs 0 1( a a ) 2( b b ) 3( c c ) 4( f f ) 5( b b ) 6( c c )
0 0 0 0 0 0 0 0
1( a a ) 0 1 1 1 1 1 1
2( b b ) 0 1 2 2 2 2 2
3( f f ) 0 1 2 2 3 3 3
4( c c ) 0 1 2 3 3 3 4
5( a a ) 0 1 2 3 3 3 4
6( b b ) 0 1 2 3 3 4 4

完整实现:

#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 :

abcdefgh
acjlfabhh

o u t p u t output :

4

猜你喜欢

转载自blog.csdn.net/qq_38790716/article/details/88076052
今日推荐