chapter12-动态规划
动态规划:就是用于求解优化问题的方法。优化问题比如说求最大值or最小值。动态规划的做法就是采用小心地策略进行暴力搜索,在多项式时间内求得最优解。
这里的小心策略就是复用子问题的解,将已解决子问题的答案保存下来,在需要子问题答案的时候便可以直接获得,而不需要重复计算,提高效率。拆分出来的子问题可以用递归的方式求解,而要复用子问题的解就要记忆化。
1.递推求解
接下来通过Fibonacci数列的求解来体会动态规划是如何降低时间复杂度的,从朴素的递归,到递归+记忆化(自顶向下求解),再到递推求解(自底向上求解)。
递归+记忆化
//Fibonacci数列求解的3种方式 2024-03-16 #include <iostream> #include <cstdio> using namespace std; const int MAXN = 100; //朴素递归求解,时间复杂度O(2^n),因为递归树的每个结点都要计算 int Fibonacci(int n) { int answer; if(n == 0 || n == 1) { answer = n; } else { answer = Fibonacci(n - 1) + Fibonacci(n - 2); } return answer; } //朴素递归+记忆化,时间复杂度O(n) int memo[MAXN]; int Fibonacci2(int n) { if(memo[n] != -1) { return memo[n]; } int answer; if(n == 0 || n == 1) { answer = n; } else { answer = Fibonacci2(n - 1) + Fibonacci2(n - 2); } memo[n] = answer; return answer; } //递推求解,时间复杂度O(n),和记忆化的本质和原理上是一模一样的 int fibo[MAXN]; int Fibonacci3(int n) { for(int i = 0; i <= n; ++i) { if(i == 0 || i == 1) { fibo[i] = i; } else { fibo[i] = fibo[i - 1] + fibo[i - 2]; } } return fibo[n]; } int main() { int n; fill(memo, memo + MAXN, -1); while(cin >> n) { // cout << Fibonacci(n) << endl; cout << Fibonacci2(n) << endl; } return 0; }
动态规划的策略非常简单,就是将原始的问题拆分成多个子问题,并将子问题的结果记录下来,以方便我们之后复用这些子问题的解。
2.最大连续子序列和
动态规划最经典的问题之一。这个问题讨论的是,在一个给定的序列
这个问题难处理就在于左右两个端点都不定,所以我们首先假定右边端点A[j]是定的,在此基础上考虑把A[j]作为末尾的最大连续子序列和为可能为多少。
最大序列和
//最大连续子序列和 2024-03-17 #include <iostream> #include <cstdio> #include <algorithm> using namespace std; const int MAXN = 1e6 + 10; const int INF = INT_MAX; long long arr[MAXN]; //朴素递归+记忆化,O(n) long long dp[MAXN]; long long MaxComponent(int n) { if(dp[n] != -1) { return dp[n]; } long long answer; if(n == 0) { answer = arr[n]; } else { answer = max(MaxComponent(n - 1) + arr[n], arr[n]); } dp[n] = answer; return answer; } //递推求解 long long dp2[MAXN]; void MaxComSum(int n) { for(int i = 0; i < n; ++i) { int answer; if(0 == i) { answer = arr[i]; } else { answer = max(dp2[i - 1] + arr[i], arr[i]); } dp2[i] = answer; } return; } int main() { int n; while(cin >> n) { fill(dp, dp + MAXN, -1); for(int i = 0; i < n; ++i) { cin >> arr[i]; } long long maximum = -INF; for(int i = 0; i < n; ++i) { maximum = max(maximum, MaxComponent(i)); } cout << maximum << endl; } return 0; }
3.最长递增子序列
动态规划最经典的问题之一。该问题描述的是在一个已知序列
同样是固定右边的一个端点,往前推这个子序列可以有几个元素。
最长上升子序列
//最长递增子序列 2024-03-17 #include <iostream> #include <cstdio> using namespace std; const int MAXN = 1000 + 10; const int INF = INT_MAX; int arr[MAXN]; //朴素递归 int Fun1(int n) { int answer; if(n == 0) { //A[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; } //朴素递归+记忆化,O(n^2) int memo[MAXN]; 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 dp[MAXN]; void Fun3(int n) { for(int j = 0; j < n; ++j) { int answer; if(j == 0) { answer = 1; } else { answer = 1; for(int i = 0; i < j; ++i) { if(arr[i] < arr[j]) { answer = max(answer, dp[i] + 1); } } } dp[j] = answer; } return; } int main() { int n; while(cin >> n) { for(int i = 0; i < n; ++i) { cin >> arr[i]; } //recursive // int maximum = 0; // for(int i = 0; i < n; ++i) { // maximum = max(maximum, Fun1(i)); // } // printf("%d\n", maximum); //recursive + memorize // fill(memo, memo + MAXN, -1); // int maximum = 0; // for(int i = 0; i < n; ++i) { // maximum = max(maximum, Fun2(i)); // } //递推 fill(dp, dp + MAXN, -1); Fun3(n); int maximum = 0; for(int i = 0; i < n; ++i) { maximum = max(maximum, dp[i]); } printf("%d\n", maximum); } return 0; }
最长公共子序列先留个坑,以后用到再说咯,也和上面的做法差不多,要固定一侧端点,分析有几种情况 ,递归出口是什么。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具