目录
基础知识
例题一(矩阵连乘)
例题二(最长公共子序列)
例题三(01背包问题)
例题四(斐波那契数列)
例题五(股票买卖的最佳时机)
例题六(最长回文子串)
例题七(零钱兑换)
1、什么问题适合使用动规?
待求解问题可以划分为若干子问题,并且子问题之间不是相互独立的。
2、分治法存在的问题?
若求解子问题不是相互独立的,分治法会重复计算公共子问题,效率低。
3、动态规划的大致思路
保存已解决的子问题答案,从而避免大量重复计算,(本质就时利用空间换取时间)
4、动态规划的结构特征
自下而上
地被求解完成。【题目描述】
给定n个矩阵{A1,A2....An}(n<100),其中Ai与Ai+1是可以相乘的,判断这n个矩阵通过加括号的方式相乘,使得相乘的次数最少。
【输入说明】
输入第一行是一个正整数,表示有n个矩阵。之后有n行数字,每行由两个正整数组成,表示第i个矩阵的行数和列数,两个数字之间用一个空格间隔。
【输出说明】
输出只有一个正整数,表示最少的相乘次数
【输入样例】
3
2 3
3 2
2 4
【输出样例】
28
#include
#define N 20
void MatrixChain(int p[N],int n,int m[N][N],int s[N][N]){
int i,j,t,k;
int r;
for(i=1;i<=n;i++){
m[i][i]=0;
}
for(r=2;r<=n;r++){
for(i=1;i<=n-r+1;i++){
j=i+r-1;
m[i][j]=m[i+1][j]+p[i-1]*p[i]*p[j];
s[i][j]=i;
for(k=i+1;k
问题描述:
给定2个序列X={x1, x2, .., xm}和Y={y1, y2, .., yn}求X和Y的最长公共自序列。
可以参考力扣1143题,1143.最长公共子序列。
以力扣题目为原型进行实现,注意力扣中用的核心代码模式,考到记得补充输入输出。
时间复杂度O(mn)
,空间复杂度O(mn)
// 力扣题目中是字符数组,如果考到int类型的数组也是同样操作
int longestCommonSubsequence(char* text1, char* text2) {
// 计算数组长度
int m = strlen(text1), n = strlen(text2);
// dp数组,1. dp[i][j] 表示text1的0 - i-1和text2的0 - j-1的公共子序列长度
int dp[m + 1][n + 1];
// 将dp数组全初始化为0,需要导入头文件string.h
// 提到头文件建议做题之前多导一些头文件
// #include #include #include #include #include
memset(dp, 0, sizeof(dp));
// 遍历顺序就是从上到下一行一行的遍历dp数组
for (int i = 1; i <= m; i++) {
char c1 = text1[i - 1]; // 这里事先拿变量存了一下,可有可无
for (int j = 1; j <= n; j++) {
char c2 = text2[j - 1]; // 这里也是,就是拿变量存了一下
// 下面就是递推公式
// 两种情况,text1[i - 1] 与 text2[j - 1]相同,text1[i - 1] 与 text2[j - 1]不相同,
// 相同的时候就用没有text1[i - 1] 和 text2[j - 1]的最大长度(即dp[i - 1][j - 1])加一
// 不相同的时候就是从没有text1[i - 1]的最大长度(即dp[i - 1][j])和没有text2[j - 1]的最大长度(即dp[i][j - 1]))选一个最大的。
if (c1 == c2) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
// 用fmax记得导包 #include
dp[i][j] = fmax(dp[i - 1][j], dp[i][j - 1]);
}
}
}
return dp[m][n];
}
问题描述:
(1)给定n种物品和一背包,物品i的重量是wi,其价值为vi,背包的容量为C。问
应如何选择装入背包的物品,使得装入背包中物品 的总价值最大?
(2)0-1背包问题是一个特殊的整数规划问题(要求背包容量,物品重量必须
是正整数)。
二维dp数组实现方式,时间复杂度O(n^2)
,空间复杂度O(n^2)
#include
#include
#include
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
#define ARR_SIZE(a) (sizeof((a)) / sizeof((a)[0]))
#define BAG_WEIGHT 4
void backPack(int* weights, int weightSize, int* costs, int costSize, int bagWeight) {
// 开辟dp数组
int dp[weightSize][bagWeight + 1];
// dp数组初始化为0
memset(dp, 0, sizeof(int) * weightSize * (bagWeight + 1));
int i, j;
// 当背包容量大于物品0的重量时,将物品0放入到背包中,即初始化第一行
for(j = weights[0]; j <= bagWeight; ++j) {
dp[0][j] = costs[0];
}
// 先遍历物品,再遍历重量,即从上到下一行一行进行遍历
for(j = 1; j <= bagWeight; ++j) {
for(i = 1; i < weightSize; ++i) {
// 如果当前背包容量小于物品重量
if(j < weights[i])
// 背包物品的价值等于背包不放置当前物品时的价值
dp[i][j] = dp[i-1][j];
// 若背包当前重量可以放置物品
else
// 背包的价值等于放置该物品或不放置该物品的最大值
dp[i][j] = MAX(dp[i - 1][j], dp[i - 1][j - weights[i]] + costs[i]);
}
}
printf("%d\n", dp[weightSize - 1][bagWeight]);
}
int main(int argc, char* argv[]) {
int weights[] = {1, 3, 4};
int costs[] = {15, 20, 30};
backPack(weights, ARR_SIZE(weights), costs, ARR_SIZE(costs), BAG_WEIGHT);
return 0;
}
一维滚动数组实现,时间复杂度O(n^2)
,空间复杂度O(n)
#include
#include
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
#define ARR_SIZE(arr) ((sizeof((arr))) / sizeof((arr)[0]))
#define BAG_WEIGHT 4
void test_back_pack(int* weights, int weightSize, int* values, int valueSize, int bagWeight) {
// 同样是dp数组然后初始化
int dp[bagWeight + 1];
memset(dp, 0, sizeof(int) * (bagWeight + 1));
int i, j;
// 先遍历物品
for(i = 0; i < weightSize; ++i) {
// 后遍历重量。从后向前遍历
for(j = bagWeight; j >= weights[i]; --j) {
// 这里是递推公式,可以理解为取 放入当前物品(dp[j - weights[i]] + values[i]) 或 不放入当前物品(dp[j]) 总价值中最高的一个
dp[j] = MAX(dp[j], dp[j - weights[i]] + values[i]);
}
}
// 打印最优结果
printf("%d\n", dp[bagWeight]);
}
int main(int argc, char** argv) {
int weights[] = {1, 3, 4};
int values[] = {15, 20, 30};
test_back_pack(weights, ARR_SIZE(weights), values, ARR_SIZE(values), BAG_WEIGHT);
return 0;
}
#include
#include
int f(int n){
int dp[100],sum=0;
dp[0] =1;
dp[1] =1;
int i;
for(i=2;i<=n;i++){
dp[i] = dp[i-1]+dp[i-2];
}
for(i=0;i
力扣股票系列的原题,直接基于力扣上的题目进行分析。
题目链接:121. 买卖股票的最佳时机
#define Max(a,b) (a>b?a:b)
int maxProfit(int* prices, int pricesSize){
int max=0;//记录最大值
int sum=0;//记录子序列和
for(int i=0;i
本题在力扣上有两种,ppt中式返回子串,还有一种式返回子串长度。
先对返回子串长度的题目进行分析,题目链接:516.最长回文子序列
int longestPalindromeSubseq(char* s) {
int n = strlen(s);
// 定义dp数组,dp[i][j] 表示字符串s在[i,j]范围内最长回文子序列的长度
int dp[n][n];
memset(dp, 0, sizeof(dp)); // 初始化为0
// 遍历顺序是从下往上一行一行的进行遍历
for (int i = n - 1; i >= 0; i--) {
dp[i][i] = 1; // 这里因为单个字母肯定式回文串,所以做个初始化
char c1 = s[i];
for (int j = i + 1; j < n; j++) {
char c2 = s[j];
// 这里是递推公式,具体思路和上面公共子序列类似。
if (c1 == c2) {
dp[i][j] = dp[i + 1][j - 1] + 2;
} else {
dp[i][j] = fmax(dp[i + 1][j], dp[i][j - 1]);
}
}
}
return dp[0][n - 1];
}
还有一种是返回子串,题目链接
力扣代码,分析的很到位了。
char * longestPalindrome(char * s){
//回文串结束下标
int j;
//最长回文串长度
int maxLength = 1;
//回文串起始位置
int begin = 0;
int len = strlen(s);
// dp[i][j] 表示 s[i..j] 是否是回文串
bool dp[len][len];
//0.初始化:所有长度为 1 的子串都是回文串
for (int i = 0; i < len; i++) {
dp[i][i] = true;
}
//1.长度为 1 的一定是回文串,故长度从 2 开始
for(int l = 2; l <= len; l++){
//2.起始位置从 0 开始
for(int i = 0; i < len; i++){
///结束位置
j = i + l - 1;
//3.注意数组越界,注意 等于
if (j >= len) {
break;
}
//回文串首尾字母不相同
if (s[i] != s[j]) {
dp[i][j] = false;
} else {
//回文串小于 3 个字符( 3 个字符内首尾相等即为回文串)
if (j - i < 3) {
dp[i][j] = true;
} else {
//回文串大于两个字符
dp[i][j] = dp[i + 1][j - 1];
}
}
//4.只要 dp[i][L] == true 成立,就表示子串 s[i:L] 是回文,此时记录回文 长度 和 起始位置
if (dp[i][j] && l > maxLength) {
maxLength = l;
begin = i;
}
}
}
//设置结束位置
s[begin + maxLength] = '\0';
return s + begin;
}
力扣原题,题目链接:322. 零钱兑换
直接贴力扣代码吧,这里记得导头文件 limits.h
int min_fun(int a, int b)
{
int c = a=coins[j] && f[i-coins[j]]!=INT_MAX)
{
//2、转移方程
f[i] = min_fun(f[i],f[i-coins[j]]+1);
}
}
}
//当凑不出时,f[amount]仍然为INT_MAX,我们需要赋值为-1,最后返回f[amount]
if(f[amount] == INT_MAX)
f[amount] = -1;
return f[amount];
}
n层台阶,一次可爬一阶或两阶,问第n阶有几种爬法
#include
#include
int f(int n){
int dp[100],sum=0;
dp[1] =1;
dp[2] = 2;
int i;
for(i=3;i<=n;i++){
dp[i] = dp[i-1]+dp[i-2];
}
return dp[n];
}
int main()
{
int n,s;
scanf("%d", &n);
s = f(n);
printf("%d", s);
return 0;
}
超: