动态规划——线性DP

这篇具有很好参考价值的文章主要介绍了动态规划——线性DP。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

动态规划——线性DP

最长不下降序列(LIS)

暴力搜索:由可行的所有起点出发,搜索出所有的路径。

动态规划——线性DP,算法学习,动态规划,代理模式,算法,c++,学习

但是深搜的算法时间复杂度要达到 O ( 2 n ) O(2^n) O(2n) (每个数都有选或不选的两个选择),指数级的时间复杂度在本题中( n ≤ 100 n≤100 n100)显然是不能接受的。那么再观察这个这棵递归树,可以发现其中有很多重复的地方。

动态规划——线性DP,算法学习,动态规划,代理模式,算法,c++,学习

那么如何优化呢?

首先可以使用数组将重复的部分记录下来,此后遇到相同的状态直接引用已经记录在数组中的数据即可,这样的方法叫做记忆化搜索,也叫剪枝(后面我们再细讲)。

所以,如果按照上面的思路将需要计算的部分用数组记录,那么就可以省略那些重复的部分,所以最终我们需要计算的就只剩下以从 1 1 1 n n n 的每个点为起点的最长不下降序列。

以序列 1 , 5 , 2 , 4 , 3 1, 5, 2, 4, 3 1,5,2,4,3 为例:

首先将以每个点为起点的所有长度都初始化为 1 1 1,所有下一步都初始化为 0 0 0

起点下标 起点数值 最长不下降序列的长度 最长不下降序列的起点的下一步的下标
5 5 5 3 3 3 l e n ( 3 ) = 1 len(3)=1 len(3)=1 0 0 0
4 4 4 4 4 4 l e n ( 4 ) = 1 len(4)=1 len(4)=1 0 0 0
3 3 3 2 2 2 l e n ( 2 ) = m a x ( l e n ( 4 ) , l e n ( 3 ) ) + 1 = 2 len(2)=max(len(4), len(3))+1=2 len(2)=max(len(4),len(3))+1=2 4 / 5 4/5 4/5
2 2 2 5 5 5 l e n ( 5 ) = 1 len(5)=1 len(5)=1 0 0 0
1 1 1 1 1 1 l e n ( 1 ) = m a x ( l e n ( 5 ) , l e n ( 2 ) , l e n ( 4 ) , l e n ( 3 ) ) + 1 = 3 len(1)=max(len(5), len(2), len(4), len(3))+1=3 len(1)=max(len(5),len(2),len(4),len(3))+1=3 3 3 3

综上,算法分析

根据动态规划的原理,由后往前进行搜索(当然从前往后也一样)。

  1. b ( n ) b(n) b(n) 来说,由于它是最后一个数,所以当从 b ( n ) b(n) b(n) 开始查找时,只存在长度为 1 1 1 的不下降序列;

  2. 若从 b ( n − 1 ) b(n-1) b(n1) 开始查找,则存在下面的两种可能性:

    ①若 b ( n − 1 ) < b ( n ) b(n-1)<b(n) b(n1)<b(n) ,则存在长度为 2 2 2 的不下降序列 b ( n − 1 ) b(n-1) b(n1) b ( n ) b(n) b(n)

    ②若 b ( n − 1 ) > b ( n ) b(n-1)>b(n) b(n1)>b(n) ,则存在长度为 1 1 1 的不下降序列 b ( n − 1 ) b(n-1) b(n1) b ( n ) b(n) b(n)

  3. 一般若从 b ( i ) b(i) b(i) 开始,此时最长不下降序列应该按下列方法求出:

    b ( i + 1 ) , b ( i + 2 ) , … , b ( n ) b(i+1),b(i+2),…,b(n) b(i+1),b(i+2),,b(n) 中,找出一个比 b ( i ) b(i) b(i) 大的且最长的不下降序列,作为它的后继。

数据结构

为算法上的需要,定义一个整数类型二维数组 b ( N , 3 ) b(N,3) b(N,3)

  1. b ( i , 1 ) b(i,1) b(i,1) 表示第 i i i 个数的数值本身;
  2. b ( i , 2 ) b(i,2) b(i,2) 表示从 i i i 位置到达 N N N 的最长不下降序列长度;
  3. b ( i , 3 ) b(i,3) b(i,3) 表示从 i i i 位置开始最长不下降序列的下一个位置,若 b [ i , 3 ] = 0 b[i,3]=0 b[i,3]=0 则表示后面没有连接项。
#include <iostream>
using namespace std;
int b[107][7];
int main()
{
    int n=0;
    while(cin>>b[++n][1]) //b[i][1]表示第i个数本身
    {
        b[n][2]=1; //b[i][2]表示以第i个数为起点的最长不下降序列的长度
        b[n][3]=0; //b[i][3]表示以第i个数为起点的最长不下降序列的起点下一步
    }
    int start=0;
    for(int i=n-1; i; --i)
    {
        int len=0, next=0; //用来记录以当前第i个点为起点的最长不下降序列的长度和下一步的下标
        for(int j=i+1; j<=n; ++j)
            if(b[i][1]<b[j][1] && b[j][2]>len) //满足这样的两个条件才能
                len=b[j][2], next=j;
        if(len) b[i][2]=len+1, b[i][3]=next;
        if(b[i][2]>b[start][2]) start=i; //找到长度最大的不下降序列,并记录当前序列的起点
    }
    cout<<b[start][2]<<endl;
    while(start)
    {
        cout<<b[start][1]<<" ";
        start=b[start][3];
    }
    return 0;
}

最长上升序列(LIS)模板:

简化题意:求一个序列的不下降序列的最大长度。

代码 1 1 1(枚举起点)

//枚举起点(从后往前枚举)
#include <iostream>
using namespace std;
const int N=1e3+7;
int a[N], f[N]; //f[i]表示以第i个数为起点的最长不下降序列的长度
int main()
{
    int n;
    cin>>n;
    for(int i=1; i<=n; ++i)
        cin>>a[i], f[i]=1; //将所有值初始化为1
    int ans=1;
    for(int i=n-1; i; --i) //从后往前枚举
    {
        for(int j=i+1; j<=n; ++j)
            if(a[i]<a[j] && f[j]+1>f[i])
                f[i]=f[j]+1;
        ans=max(ans, f[i]);
    }
    cout<<ans;
    return 0;
}

代码 2 2 2(枚举终点)

//枚举终点(从前往后枚举)
#include <iostream>
using namespace std;
const int N=1e3+7;
int a[N], f[N]; //f[i]表示以第i个数为终点的最长不下降序列的长度
int main()
{
    int n;
    cin>>n;
    for(int i=1; i<=n; ++i)
        cin>>a[i], f[i]=1;
    int ans=1;
    for(int i=2; i<=n; ++i) //从前往后枚举
    {
        for(int j=1; j<i; ++j)
            if(a[j]<a[i] && f[j]+1>f[i])
                f[i]=f[j]+1;
        ans=max(ans, f[i]);
    }
    cout<<ans;
    return 0;
}

模板优化

上述代码的时间复杂度为 O ( n 2 ) O(n^2) O(n2) ,所以如果把数据范围改为 n ≤ 100000 n≤100000 n100000 也会出现超时的情况,所以在这里对于求最长不下降序列长度的问题还可以进一步优化。在这里对上述代码 2 2 2 的思路进一步思考。(优化的过程其实就是一个去除冗余的过程。)

用以下样例作为引子:

Input:
7
3 1 2 1 8 5 6
Output:
4 

枚举每一个点作为终点,从第一个数 3 3 3 开始, 3 3 3 可以作为一个长度为 1 1 1 的最长不下降序列,接着到第二个数 1 1 1,也是一个长度为 1 1 1 的长度为 1 1 1 的最长不下降序列……,对于后面的每个数,如果它可以接到 3 3 3 的后面,那么它一定可以接到 1 1 1 的后面,所以以 3 3 3 为结尾长度为 1 1 1 的最长不下降序列就没有必要存下来了,因为 1 1 1 3 3 3 更优(后面可以接的数范围更广,即更小的数值作为结尾更优)。所以我们也没有必要将所有长度为 1 1 1 的序列都存下来,只需要每次存下那个相同长度下最优的情况的结尾数值即可。

那么对于当前的不下降子序列的长度 i i i,一定有一个结尾值 f [ i ] f[i] f[i],则有

结论:对于不同长度的序列的结尾数值一定是随着序列长度的增加而严格单调递增的。

证明(反证法):

假设存在长度为 i i i 的序列结尾数值 f [ i ] f[i] f[i] 小于或等于长度为 i − 1 i-1 i1 的序列结尾数值 f [ i − 1 ] f[i-1] f[i1] ,即 f [ i ] ≤ f [ i − 1 ] f[i]≤f[i-1] f[i]f[i1]

由于每个序列都是一个不下降序列,所以当前长度为 i i i 的序列的倒数第二个数 x x x 一定满足关系: x ≤ f [ i ] ≤ f [ i − 1 ] x≤f[i]≤f[i-1] xf[i]f[i1]。但若如此,在前面遍历的时候,对于长度为 i − 1 i-1 i1 的序列的结尾更优的结果应该选择的是比当前 f [ i − 1 ] f[i-1] f[i1] 更小的 x x x,所以这与我们最终选择的 f [ i − 1 ] f[i-1] f[i1] 相悖。

所以假设(存在长度为 i i i 的序列结尾数值 f [ i ] f[i] f[i] 小于或等于长度为 i − 1 i-1 i1 的序列结尾数值 f [ i − 1 ] f[i-1] f[i1] ,即 f [ i ] ≤ f [ i − 1 ] f[i]≤f[i-1] f[i]f[i1])不成立。

所以对于不同长度的序列的结尾数值一定是随着序列长度的增加而严格单调递增的结论成立。

动态规划——线性DP,算法学习,动态规划,代理模式,算法,c++,学习

有了这样的前提,如果想求以 a [ i ] a[i] a[i] 结尾的最长不下降子序列的长度应该如何解决呢?

届时,只需要找到一个最大的比 a [ i ] a[i] a[i] 小的数 f [ j ] f[j] f[j],并将 a [ i ] a[i] a[i] 接到其后面即可,这样所得到的以 a [ i ] a[i] a[i] 为结尾的最长不下降子序列的长度就是 j + 1 j+1 j+1。这时还需要更新一下, f [ j + 1 ] f[j+1] f[j+1],因为长度为 j + 1 j+1 j+1 的序列的结尾 a [ i ] a[i] a[i] 一定是小于先前的长度为 j + 1 j+1 j+1 的序列的结尾数的。

那么这个过程中如何找到这个最大的比 a [ i ] a[i] a[i] 小的数呢?肯定不能选择逐个遍历这样的方法的,因为这样的时间复杂度就又回到了优化前的 O ( n 2 ) O(n^2) O(n2)

这时就用到前面的结论了:对于不同长度的序列的结尾数值一定是随着序列长度的增加而严格单调递增的结论成立。既然是严格单调上升的序列,寻找其中一个数,我们就可以想到使用二分法。

简单介绍二分法(具体我们后面会在学习分治思想的时候继续讲):二分查找也称折半查找,顾名思义,就是每次查找去掉不符合条件的一半区间,直到找到答案(整数二分)或者和答案十分接近(浮点二分)。

#include <iostream>
using namespace std;
const int N=1e5+7;
int a[N], f[N]; //f[i]表示长度为i的最长不下降子序列的最后一个值
int main()
{
    int n;
    cin>>n;
    for(int i=1; i<=n; ++i) cin>>a[i];
    int len=0;
    f[1]=-2e9;
    for(int i=1; i<=n; ++i)
    {
        int l=0, r=len;
        while(l<r)
        {
            int mid=l+r+1>>1;
            if(f[mid]<a[i]) l=mid;
            else r=mid-1;
        }
        len=max(len, r+1);
        f[r+1]=a[i];
    }
    cout<<len;
    return 0;
}

最长公共子序列(LCS)

状态表示:

f [ i ] [ j ] f[i][j] f[i][j] 表示 s 1 s1 s1 的前 i i i 个字符与 s 2 s2 s2 的前 j j j 个字符的最大公共子序列的长度了

设字符串 s 1 s1 s1 s 2 s2 s2 的长度分别为 l e n 1 len1 len1 l e n 2 len2 len2,则 s 1 s1 s1 s 2 s2 s2 的最大公共子序列的长度为 f [ l e n 1 ] [ l e n 2 ] f[len1][len2] f[len1][len2]

状态初始化:

f [ l e n 1 ] [ 0 ] = 0 f[len1][0]=0 f[len1][0]=0 f [ 0 ] [ l e n 2 ] = 0 f[0][len2]=0 f[0][len2]=0

状态转移:(分为三种情况讨论)

s 1 [ i ] s1[i] s1[i] 不在公共子序列中:该情况下 f [ i ] [ j ] = f [ i − 1 ] [ j ] f[i][j]=f[i-1][j] f[i][j]=f[i1][j]

s 2 [ j ] s2[j] s2[j] 不在公共子序列中:该情况下 f [ i ] [ j ] = f [ i ] [ j − 1 ] f[i][j]=f[i][j-1] f[i][j]=f[i][j1]

s 1 [ i ] s1[i] s1[i] s 2 [ j ] s2[j] s2[j] 都在公共子序列中(且 s 1 [ i ] = s 2 [ j ] s1[i]=s2[j] s1[i]=s2[j]):该情况下 f [ i ] [ j ] − f [ i − 1 ] [ j − 1 ] + 1 f[i][j]-f[i-1][j-1]+1 f[i][j]f[i1][j1]+1

f [ i ] [ j ] f[i][j] f[i][j] 取上述三种情况的最大值(第三种情况要求 s 1 [ i ] = s 2 [ j ] s1[i]=s2[j] s1[i]=s2[j]

s 1 = B D C A B A s1=BDCABA s1=BDCABA s 2 = A B C B D A B s2=ABCBDAB s2=ABCBDAB 两个字符串为例:

A B C B D A B
s 1 s1 s1\ s 2 s2 s2
0 0 0 0 0 0 0 0
B 0 0 1 1 1 1 1 1
D 0 0 1 1 1 2 2 2
C 0 0 1 2 2 2 2 2
A 0 1 1 2 2 2 3 3
B 0 1 2 2 3 3 3 4
A 0 1 2 2 3 3 4 4

代码

#include <iostream>
#include <cstring>
using namespace std;
const int N=207;
char s1[N], s2[N];
int f[N][N];
int main()
{
	scanf("%s%s", s1+1, s2+1); //注意将下标为0的位置空出来,避免后面的状态
	for(int i=1; s1[i]; ++i)
		for(int j=1; s2[j]; ++j)
			if(s1[i]==s2[j]) f[i][j]=f[i-1][j-1]+1;
			else f[i][j]=max(f[i][j-1], f[i-1][j]);
	int len1=strlen(s1+1), len2=strlen(s2+1);
	cout<<f[len1][len2];
	return 0;
}

最长公共上升子序列(LCIS)

动态规划——线性DP,算法学习,动态规划,代理模式,算法,c++,学习

朴素

首先按照上述思路用代码实现出来:

#include <iostream>
using namespace std;
const int N=3e3+7;
int a[N], b[N], f[N][N];
//f[i][j]表示所有在a[1~i]和b[1~j]中都出现过
//且以b[j]结尾的公共上升子序列的集合
int main()
{
	int n;
	cin>>n;
	for(int i=1; i<=n; ++i) cin>>a[i];
	for(int i=1; i<=n; ++i) cin>>b[i];
	for(int i=1; i<=n; ++i)
		for(int j=1; j<=n; ++j)
		{
			f[i][j]=f[i-1][j]; //a[i]!=b[j](以b[j]结尾且不包含a[i])
			if(a[i]==b[j])
			{
				int mx=0;
				for(int k=1; k<j; ++k) //遍历找出b[j]前一个是由哪个状态转移过来的
					if(b[k]<b[j]) //满足“上升”条件
						mx=max(f[i-1][k]+1, mx); //加上的1就是当前的b[j]
				f[i][j]=max(f[i][j], mx);
			}
		}
	int ans=0;
	for(int i=1; i<=n; ++i)
		ans=max(f[n][i], ans);
	cout<<ans;
	return 0;
}

优化

上述代码一个明显的弊端在于它的三层循环,时间复杂度达到了 O ( n 3 ) O(n^3) O(n3)。那么如何优化呢?显然可以从最内层的循环下手。

for(int k=1; k<j; ++k)
	if(b[k]<b[j])
		mx=max(f[i-1][k]+1, mx);

这里有一个小细节的处理:当前循环是在 a [ i ] = = b [ j ] a[i]==b[j] a[i]==b[j] 的条件下的,所以循环中的判断条件 b [ k ] < b [ j ] b[k]<b[j] b[k]<b[j] 也可以改成 b [ k ] < a [ i ] b[k]<a[i] b[k]<a[i],即:

for(int k=1; k<j; ++k)
	if(b[k]<a[i])
		mx=max(f[i-1][k]+1, mx);

思考:该循环的目的是什么呢?

每个 b [ k ] b[k] b[k] 都对应一个状态 f [ i − 1 , k ] f[i-1,k] f[i1,k]。那么当前循环相当于是在 b [ 1 ] , b [ 2 ] , b [ 3 ] , . . . , b [ j − 1 ] b[1], b[2], b[3],...,b[j-1] b[1],b[2],b[3],...,b[j1] 中找出所有小于 a [ i ] a[i] a[i] 的数,并在比较他们分别对应的 f [ i − 1 , k ] f[i-1,k] f[i1,k] 之后选出一个最大值,作为当前状态的上一个状态。这些状态的大小都是固定不变的,所以在当前一轮中我们比较了 f [ i − 1 , 1 ] , f [ i − 1 , 2 ] , f [ i − 1 , 3 ] , . . . , f [ i − 1 , j − 1 ] f[i-1, 1], f[i-1, 2], f[i-1, 3],..., f[i-1, j-1] f[i1,1],f[i1,2],f[i1,3],...,f[i1,j1] 之后,在下一轮比较 f [ i − 1 , 1 ] , f [ i − 1 , 2 ] , f [ i − 1 , 3 ] , . . . , f [ i − 1 , j − 1 ] , f [ i − 1 ] [ j ] f[i-1, 1], f[i-1, 2], f[i-1, 3],...,f[i-1, j-1], f[i-1][j] f[i1,1],f[i1,2],f[i1,3],...,f[i1,j1],f[i1][j] 的时候又将前面的这些拿过来重复对比了一次,所以这里多了很多重复的部分。那么想要将这层循环优化掉,则可以直接比较当前 b [ j ] b[j] b[j] a [ i ] a[i] a[i] 即可:只有 a [ i ] = b [ j ] a[i]=b[j] a[i]=b[j] 的时候才更新当前的 f [ i ] [ j ] f[i][j] f[i][j],只有当 b [ j ] < a [ i ] b[j]<a[i] b[j]<a[i] 的时候我们才要去更新前缀的最大值。

如图示:当前被✔的所有数,在之后比较中还会被再拿出来进行比较。

动态规划——线性DP,算法学习,动态规划,代理模式,算法,c++,学习

#include <iostream>
using namespace std;
const int N=3e3+7;
int a[N], b[N], f[N][N];
int main()
{
	int n;
	cin>>n;
	for(int i=1; i<=n; ++i) cin>>a[i];
	for(int i=1; i<=n; ++i) cin>>b[i];
	for(int i=1; i<=n; ++i)
	{
		int mx=1;
		for(int j=1; j<=n; ++j)
		{
			f[i][j]=f[i-1][j];
			if(a[i]==b[j]) f[i][j]=max(mx, f[i][j]); //更新f[i][j]这个状态值
			else if(b[j]<a[i]) mx=max(mx, f[i-1][j]+1); //更新前缀的最大值
		}
	}
	int ans=0;
	for(int i=1; i<=n; ++i)
		ans=max(f[n][i], ans);
	cout<<ans;
	return 0;
}

再优化

将状态数组由二维压缩成一维:

#include <iostream>
using namespace std;
const int N=3e3+7;
int a[N], b[N], f[N];
int main()
{
	int n;
	cin>>n;
	for(int i=1; i<=n; ++i) cin>>a[i];
	for(int i=1; i<=n; ++i) cin>>b[i];
	for(int i=1; i<=n; ++i)
	{
		int mx=1;
		for(int j=1; j<=n; ++j)
			if(a[i]==b[j])
				f[j]=max(mx, f[j]);
			else if(b[j]<a[i])
				mx=max(mx, f[j]+1);
	}
	int ans=0;
	for(int i=1; i<=n; ++i)
		ans=max(f[i], ans);
	cout<<ans;
	return 0;
}

拦截导弹(missile)

该问题的第一问是要求一个最长不上升序列的长度,典型的LIS问题。

第二问用贪心的思路做:

贪心流程:从前往后扫描每个数,对于每个数:

  • 情况 1 1 1:如果现有的子序列的结尾都小于当前数,则创建新的子序列
  • 情况 2 2 2:将当前数放到结尾大于等于它的最小子序列后面

A A A 为贪心算法得到的序列个数; B B B 表示最优解;如果能证明 A ≥ B A≥B AB 并且 A ≤ B A≤B AB,那么就可以得到 A = B A=B A=B

因为最优解一定是最少的答案,所以贪心算法的结果一定大于等于最优解,即 A ≥ B A≥B AB

那么接下来如何证明 A ≤ B A≤B AB 呢?可以使用调整法:假设最优解对应的方案数与贪心算法算出当前的方案不同。

找到第一个不同的数:假设当前贪心算法找到的方案应该将当前的 x x x 接到 a a a 的后面,即 a a a 是大于等于 x x x 的最小子序列的结尾;而最优解这个时候是将 x x x 接到 b b b 的后面,由于 a a a 是大于等于 x x x 的最小数,所以一定有 b ≥ a b≥a ba,那接下来两种结果引出来的序列,是可以进行交换的(因为 x ≤ a ≤ b x≤a≤b xab,所以 x x x 可以接在 a a a 后面,也可以接在 b b b 后面),调换后的结果并不影响最终的序列个数。
动态规划——线性DP,算法学习,动态规划,代理模式,算法,c++,学习

所以当前方案即使不同,贪心算法最终算出来的方案数也是与最优解的方案数是相同的。所以当前贪心算法就是最优算法。

代码 1 1 1

#include <iostream>
using namespace std;
const int N=1e3+7;
int n, a[N], f[N], g[N];
int main()
{
    while(cin>>a[++n]);
    int res=0;
    for(int i=1; i<=n; ++i)
    {
        for(int j=1; j<i; ++j)
            if(a[j]>=a[i])
                f[i]=max(f[i], f[j]+1);
        res=max(res, f[i]);
    }
    cout<<res<<endl;
    int cnt=0;
    for(int i=1; i<=n; ++i)
    {
        int k=0;
        while(k<cnt && g[k]<a[i]) k++;
        g[k]=a[i];
        if(k>=cnt) cnt++;
    }
    cout<<cnt<<endl;
    return 0;
}

对于一个序列,最少用到的可将其覆盖的非上升子序列的个数与最大上升子序列的长度是相同的,这两个问题是一个对偶问题(离散数学中的反链定理Dilworth定理)。

所采用的做法完全相同 = > => => 最终求得的结果完全相同 = > => => 解决的问题完全相同

代码 2 2 2文章来源地址https://www.toymoban.com/news/detail-803157.html

#include <iostream>
using namespace std;
const int N=1e3+7;
int a[N], f1[N], f2[N];
int main()
{
    int n=0, ans1=0, ans2=0;
    while(scanf("%d", &a[++n])!=EOF) f2[n]=1;
    for(int i=1; i<=n; i++)
    {
        for(int j=1; j<i; j++)
        {
            if(a[j]>=a[i]) f1[i]=max(f1[i], f1[j]+1);
            else f2[i]=max(f2[i], f2[j]+1);
        }
        ans1=max(ans1, f1[i]);
        ans2=max(ans2, f2[i]);
    }
    printf("%d\n%d", ans1, ans2);
    return 0;
}

到了这里,关于动态规划——线性DP的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 猿创征文 |【算法面试入门必刷】动态规划-线性dp(四)

    📦个人主页:一二三o-0-O的博客 🏆技术方向:C/C++客户端资深工程师(直播+音视频剪辑) 👨‍💻作者简介:数据结构算法与音视频领域创作者 📒 系列专栏:牛客网面试必刷 📣专栏目标:帮助伙伴们通过系统训练,掌握数据结构与算法,收获心仪Offer 📝推荐一个找工作

    2024年02月02日
    浏览(28)
  • 动态规划(DP)入门——线性DP

    在了解线性DP之前,我们首先要知道什么是动态规划,即为将一种复杂问题,分解成很多重叠的子问题,并通过子问题的解得到整个问题的解的算法。听起来比较抽象,动态规划简单来说就是确定问题的状态,通常题目都会提示,一般为“到第i个为止,xx为j(xx为k)的方案数/最

    2024年02月19日
    浏览(31)
  • 动态规划:线性DP

    2024年02月06日
    浏览(32)
  • 动态规划——线性DP

    暴力搜索:由可行的所有起点出发,搜索出所有的路径。 但是深搜的算法时间复杂度要达到 O ( 2 n ) O(2^n) O ( 2 n ) (每个数都有选或不选的两个选择),指数级的时间复杂度在本题中( n ≤ 100 n≤100 n ≤ 100 )显然是不能接受的。那么再观察这个这棵递归树,可以发现其中有很

    2024年01月19日
    浏览(35)
  • 动态规划算法学习一:DP的重要知识点、矩阵连乘算法

    三部曲如下三步: 基本原则:“空间换时间” 存储重复子问题的解,减少运算时间 底层运算:“表格操作” 用表格存储子问题的解 实现路线:“子问题划分、自底向上求解” 利用表格中存储的子问题的解,求上一层子问题的解。 矩阵连乘计算次序 可以用 加括号的方式

    2024年02月09日
    浏览(32)
  • [动态规划]——线性DP(LIS/LCS/LCIS等) 详解

    线性DP,是较常见的一类动态规划问题,其是在线性结构上进行状态转移,这类问题不像背包问题、区间DP等有固定的模板 线性动态规划的目标函数为特定变量的线性函数,约束是这些变量的线性不等式或等式,目的是求目标函数的最大值或最小值 因此,除了少量问题(如:

    2024年02月10日
    浏览(32)
  • DP算法:动态规划算法

    (1)确定初始状态 (2)确定转移矩阵,得到每个阶段的状态,由上一阶段推到出来 (3)确定边界条件。 蓝桥杯——印章(python实现) 使用dp记录状态,dp[i][j]表示买i张印章,凑齐j种印章的概率 i表示买的印章数,j表示凑齐的印章种数 情况一:如果ij,不可能凑齐印章,概

    2024年02月07日
    浏览(42)
  • 【LeetCode: 2369. 检查数组是否存在有效划分 | 暴力递归=>记忆化搜索=>动态规划 | 线性dp】

    🚀 算法题 🚀 🌲 算法刷题专栏 | 面试必备算法 | 面试高频算法 🍀 🌲 越难的东西,越要努力坚持,因为它具有很高的价值,算法就是这样✨ 🌲 作者简介:硕风和炜,CSDN-Java领域新星创作者🏆,保研|国家奖学金|高中学习JAVA|大学完善JAVA开发技术栈|面试刷题|面经八股文

    2023年04月19日
    浏览(32)
  • ★动态规划(DP算法)详解

    什么是动态规划:动态规划_百度百科 内容太多了不作介绍,重点部分是无后效性,重叠子问题,最优子结构。 问S-P1和S-P2有多少种路径数,毫无疑问可以先从S开始深搜两次,S-P1和S-P2找出所有路径数,但是当这个图足够大,那就会超时。 动态规划旨在用 空间换时间 ,我们

    2024年02月04日
    浏览(41)
  • 算法——动态规划(DP)——递推

    动态规划常用于解决优化问题。 动态规划通常以自底向上或自顶向下的方式进行求解。 自底向上的动态规划从最简单的子问题开始,逐步解决更复杂的问题,直到达到原始问题。 自顶向下的动态规划则从原始问题出发,分解成子问题,并逐步求解这些子问题。 动态规划算法

    2024年01月20日
    浏览(46)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包