动态规划

动态规划

求解优化问题的方法

小心+暴力搜索

子问题+复用

递推求解:求fibonacci数列

朴素递归

时间复杂度O(2n)

int Fibonacci1(int n){
	int answer;
	if(n == 0 || n == 1){
		answer = n;
	}
	else{
		answer = Fibonacci(n-1) + Fibonacci(n-2);
	}
	return answer;
}

朴素递归+记忆化

记忆化:求解过程中每求出一个子问题的解,就记录下来,后续求解过程如果需要该子问题的解,直接查表使用即可

时间复杂度O(n)

const int MAXN = 100;
int memo[MAXN];		//初始化所有元素为-1
int Fibonacci2(int n){
    if(memo[n] != -1){
        return memo[n];
    }    	
    int answer;
	if(n == 0 || n == 1){
		answer = n;
	}
	else{
		answer = Fibonacci(n-1) + Fibonacci(n-2);
	}
    memo[n] = answer;
	return answer;
}

这就使用动态规划思想,通过记忆化减少重复计算的次数

递推求解

和朴素递归+记忆化原理一样

时间复杂度O(n)

const int MAXN = 100;
int fib[MAXN];		//初始化所有元素为-1
int Fibonacci3(int n){
	for(int i = 0; i <= n; ++i){
        int answer;
        if(i == 0 || i == 1){
            answer = i;
        }
        else{
            answer = fib[i-1] + fib[i-2];
        }
    }
    return answer;
}
image-20220209080534301

递归+记忆化求解:从右往左求

递推求解:从左往右求

最大连续子序列和

直接考虑该问题的求解比较困难,因为子序列的两端都在动态变化,可以考虑将端固定下来,求以某一个元素作为末尾元素的情况下其最大连续子序列之和,

image-20220209081450481

其中Fj是以Aj为结尾的最大连续子序列和

朴素递归

存在大量重复计算,时间复杂度O(n2)

#include<stdio.h>
#include<iostream>
#include<climits>
using namespace std;

#define ll long long

const ll INF = INT_MAX;
const int MAXN = 1E6 + 10;
ll arr[MAXN];

ll fun1(int n){
    ll answer;
    if(n == 0){
        answer = arr[n];
    }
    else{
        answer = max(arr[n], fun1(n-1) + arr[n]);
    }
    return answer;
}

int main(){
    int n;
    while(scanf("%d", &n ) != EOF){
        for(int i = 0; i < n; ++i){
            scanf("%lld", &arr[i]);
        }
		
        //遍历arr,求以arr[i]为结尾的子序列和的最大值
        ll maximum = -INF;
        for(int i = 0; i < n; ++i){
            maximum = max(maximum, fun1(i));
        }
        printf("%lld\n", maximum);
    }
    return 0;
}

递归+记忆化

消除重复计算,时间复杂度O(n)

ll memo[MAXN];	//初始化为-1
ll fun2(int n){
    if(memo[n] != -1){	//之前已经计算过的子问题,直接查表
        return memo[n];
    }
    ll answer;
    if(n == 0){
        answer = arr[n];
    }
    else{
        answer = max(arr[n], fun2(n-1) + arr[n]);
    }
    memo[n] = answer;	//记录当前子问题的解
    return answer;
}

递推求解

时间复杂度O(n)

ll dp[MAXN];	//初始化为-1
void fun3(int n){
    for(int i = 0; i < n; ++i){
        ll answer;
        if(i == 0){
            answer = arr[i];
        }
        else{
            answer = max(arr[i], dp[i-1] + arr[i]);
        }
        dp[i] = answer;
    }
    return ;
}

最长递增子序列

借用最大连续子序列和问题求解思想,考虑将子序列某一端固定下来,即求以某个元素为末尾元素下的最长递增子序列

image-20220209155641596

Fj为以Aj为结尾的最长递增子序列

注:最长递增子序列问题和最大连续子序列和问题的区别在于,后者要求连续,而前者不要求连续。所以,在求Fj时,要考虑以前面所有元素为结尾的最长递增子序列。

朴素递归

存在大量重复计算,时间复杂度O(2n)

#include<stdio.h>
#include<iostream>
using namespace std;


const int MAXN = 1000 + 10;
int arr[MAXN];

int Fun1(int n){
    int answer;
    if(n == 0){
        answer = 1;
    }
    else{
        answer = 1;
        for(int i = 0; i < n; ++i){
            if(arr[i] < arr[n]){
                answer = max(answer, Fun1(i) + 1);
            }
        }
    }
    return answer;
}

int main(){
    int n;
    scanf("%d", &n);
    for(int i = 0; i < n; ++i){
        scanf("%d", &arr[i]);
    }
    //recursive
    int maxmium = 0;
    for(int i = 0; i < n; ++i){
        maxmium = max(maxmium, Fun1(i));
    }
    printf("%d\n", maxmium);
    return 0;
}

递归策略+记忆化

memo记录已经计算过的子问题,时间复杂度O(n2)

#include<stdio.h>
#include<iostream>
using namespace std;


const int MAXN = 1000 + 10;

int memo[MAXN];		//初始化为-1
int Fun2(int n){
    if(memo[n] != -1){	//子问题如果已经计算过,直接查表
        return memo[n];
    }
    int answer;
    if(n == 0){
        answer = 1;
    }
    else{
        answer = 1;
        for(int i = 0; i < n; ++i){
            if(arr[i] < arr[n]){
                answer = max(answer, Fun2(i) + 1);
            }
        }
    }
    memo[n] = answer;	//记录子问题
    return answer;
}

int main(){
    int n;
    scanf("%d", &n);
    for(int i = 0; i < n; ++i){
        scanf("%d", &arr[i]);
    }
    fill(memo, memo + n, -1);
    int maxmium = 0;
    for(int i = 0; i < n; ++i){
        maxmium = max(maxmium, Fun2(i));
    }
    printf("%d\n", maxmium);
    return 0;
}

递推求解

时间复杂度O(n2)

#include<stdio.h>
#include<iostream>
using namespace std;


const int MAXN = 1000 + 10;
int arr[MAXN];

int dp[MAXN];

void Fun3(int n){
    for(int i = 0; i < n; ++i){
        int answer;
        if(i == 0){
            answer = 1;
        }
        else{
            answer = 1;
            for(int j = 0; j < i; ++j){
                if(arr[j] < arr[i]){
                    answer = max(answer, dp[j] + 1);
                }
            }
        }
        dp[i] = answer;
    }
    return ;
}

int main(){
    int n;
    scanf("%d", &n);
    for(int i = 0; i < n; ++i){
        scanf("%d", &arr[i]);
    }

    fill(dp, dp + n, -1);
    Fun3(n);

    int maxmium = 0;
    for(int i = 0; i < n; ++i){
        maxmium = max(maxmium, dp[i]);
    }
    printf("%d\n", maxmium);
    return 0;
}

最长公共子序列

对于A1,A2...Ai和B1,B2...Bj

image-20220209164603166

其中Fi,j是以Ai和Bj结尾的最长公共子序列长度

朴素递归

时间复杂度O(2n+m)

#include<cstdio>
#include<iostream>
#include<string.h>
using namespace std;

const int MAXN = 1000 + 10;
char str1[MAXN];
char str2[MAXN];

int Fun1(int n, int m){
    int answer;
    if(n == 0 || m == 0){
        answer = 0;
    }
    else{
        if(str1[n] == str2[m]){
            answer = Fun1(n-1, m-1) + 1;
        }
        else{
            answer = max(Fun1(n, m-1), Fun1(n-1, m));
        }
    }
    return answer;
}

int main(){
    while(scanf("%s%s", str1+1, str2+1) != EOF){
        int n = strlen(str1 + 1);
        int m = strlen(str2 + 1);
        int maxmium = Fun1(n, m);
        printf("%d\n", maxmium);
    }
    return 0;
}

递归策略+记忆化

时间复杂度O(n*m)

#include<cstdio>
#include<iostream>
#include<string.h>
using namespace std;

const int MAXN = 1000 + 10;
char str1[MAXN];
char str2[MAXN];

int memo[MAXN][MAXN];	//记录,初始化均为-1表示该问题尚未被求解

int Fun2(int n, int m){
    if(memo[n][m] != -1){	//子问题已经求过,直接查表
        return memo[n][m];
    }
    int answer;
    if(n == 0 || m == 0){
        answer = 0;
    }
    else{
        if(str1[n] == str2[m]){
            answer = Fun2(n-1, m-1) + 1;
        }
        else{
            answer = max(Fun2(n, m-1), Fun2(n-1, m));
        }
    }
    memo[n][m] = answer;	//记录子问题的解
    return answer;
}


int main(){
    while(scanf("%s%s", str1+1, str2+1) != EOF){
        int n = strlen(str1 + 1);
        int m = strlen(str2 + 1);

        for(int i = 0; i <= n; ++i){
            for(int j = 0; j <= m; ++j){
                memo[i][j] = -1;
            }
        }

        int maxmium = Fun2(n, m);
        printf("%d\n", maxmium);
    }
    return 0;
}

递推求解

时间复杂度O(n*m)

#include<cstdio>
#include<iostream>
#include<string.h>
using namespace std;

const int MAXN = 1000 + 10;
char str1[MAXN];
char str2[MAXN];

int dp[MAXN][MAXN];
void Fun3(int n, int m){
    for(int i = 0; i <= n; ++i){
        for(int j = 0; j <= m; ++j){
            int answer;
            if(i == 0 || j == 0){
                answer = 0;
            }
            else{
                if(str1[i] == str2[j]){
                    answer = dp[i-1][j-1] + 1;
                }
                else{
                    answer = max(dp[i][j-1], dp[i-1][j]);
                }
            }
            dp[i][j] = answer;
        }
    }
    return;
}


int main(){
    while(scanf("%s%s", str1+1, str2+1) != EOF){
        int n = strlen(str1 + 1);
        int m = strlen(str2 + 1);

        Fun3(n, m);
        int maxmium = dp[n][m];
        printf("%d\n", maxmium);
    }
    return 0;
}

posted @ 2022-02-10 09:29  dctwan  阅读(17)  评论(0编辑  收藏  举报