相信很多初学者,在刚刚接触动态规划,都花费了不少时间。网上有很多人写如何去做动态规划的题目,但是很少有整理适合初学者做的习题。这里是我自己考研期间,复习整理的。希望对大家有帮助。
c动态规划精简例题
1. 单序列型
- 爬楼梯
算法思想:
1、定边界体条件,因为有两种走法,所以dp[0]=cost[0],dp[1]=cost[1]
2、我们假设第i个为最后一步,则有两种情况
min{dp[i-1],dp[i-2]}+sonst[i]
//1.爬楼梯
int minCostClimbingStairs(int cost[], int n)
{
int dp[n];
dp[0] = cost[0];
dp[1] = cost[1];
for (int i = 2; i < n; i++)
{
dp[i] = min{dp[i - 1], dp[i - 2]} + cost[i];
}
return min{dp[n - 2], dp[n - 1]};
}
- House Robber
算法思想:
1、边界条件:如果没有房子,则return 0;
2、如果只有一个房子,则返回这一个房子
3、dp[0]=nums[0];
dp[1]=max{nums[0],nums[1]};
4、比较第i个房子偷(dp[i - 2] + nums[i])和不偷(dp[i - 1])的时候的大小
//2.House Robber
int rob(int nums[], int n)
{
if (n == 0)
return 0;
if (n == 1)
return nums[0];
int dp[n];
dp[0] = nums[0];
dp[1] = max{nums[0], nums[1]};
for (int i = 2; i < n; i++)
{
dp[i] = max(dp[i - 1], dp[i - 2] + nums[i]);
}
return dp[n - 1];
}
- 最长上升子序列
算法思想:这里用到的是一个自底向上的寻找最优子结构的的思想。粗俗来说:如果你想要得到七个数里面的最长子序列,你可以先找前6个数里面的最长子序列,同理,你又必须得找前5个数里面的最长子序列,直到子序列为1
1、我们先令dp[i]=1;//初始化,假如刚开始前面没有比它小的元素只有它自己
2、我们在0~i之间,与dp[i]进行比较,如果,比dp[i]大,则dp[i]=dp[k]+1
3、一趟内for循环结束,确定一个空,这个时候和max进行比较。因为最长上升子序列不一定是最后一个,所以每次都要比较下
//3.最长上升子序列
int lengthOfLIS(int nums[], int n)
{
if (n == 0)
return 0;
int dp[n];
dp[0] = 1;
max = 1;
for (int i = 1; i < n; i++)
{
dp[i] = 1;//一开始最长上升子序列只有自己1个
for (int k = 0; k < i; k++)
{
if (nums[k] < nums[i])//与比nums[i]小的之前的所有的最长上升子序列进行比较
dp[i] = max{dp[i], dp[k] + 1};
}
max = max{max, dp[i]};//最长上升子序列不一定是最后一个,所以每次都要比较下
}
return max;
}
- (模拟卷上的例题)
![8d2c882c1e3a5fd5c4346a69bcdc9afb.png](https://img-blog.csdnimg.cn/img_convert/8d2c882c1e3a5fd5c4346a69bcdc9afb.png)
.JPG)
算法思想:
1、我们用一个一维数组来统计输入的数据的个数
2、我们a来存储最大值,作为后面for循环的终止条件
3、我们每次到i的时候都是假设i为最后一个元素,与前面的状态进行比较
a.如果第i个数据我们不选择,则dp[i-1]
b.如果我们选择,则dp[i-2]+vis[i]*i
4、找里面最大值
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
define N 1000 int main()
{
int n, a, m = 0;
int dp[N]; //dp[i]为数字1~i能获得的最大价值
int vis[dp]; //用来统计序列中数字i的个数
memset(vis, 0, sizof(vis));
scanf("%d", &n);
for (int i = 0; i < n; i++)
{
scanf("%d", &a);
vis[a]++;
m = max(m, a);
}
dp[0] = 0;
dp[1] = vis[1];
for (int i = 2; i < m; i++)
{
dp[i] = max{dp[i - 1], dp[i - 2] + vis[i] * i};
}
printf("%dn", dp[m]);
return 0;
}
2.双序列型
- 最长公共子序列
转移方程: $$ dpleft[ i right] left[ j right] =begin{cases} 0 & & {i=0text{或}j=0} dpleft[ i-1 right] left[ j-1 right] +1 & & {Aleft[ i right] =Bleft[ j right]} 0 & & {Aleft[ i right] ne Bleft[ j right]} end{cases} $$
核心算法:
if (A[i] == B[j]) //如果最后一个元素相同
dp[i][j] = dp[i - 1][j - 1] + 1;
else
dp[i][j] = max{dp[i - 1][j], dp[i][j - 1]};
void LCSLength(int m, int n, char *x, char *y, int dp[][])
{
int i, j;
for (i = 1; i <= m; i++)
dp[i][0] = 0;
for (i = 1; i <= n; i++)
dp[0][i] = 0;
for (i = 1; i <= m; i++)
{
for (j = 1; j <= n; j++)
{
if (x[i] == y[j])
dp[i][j] = dp[i - 1][j - 1] + 1;
else
{
dp[i][j] = max{dp[i - 1][j], dp[i][j - 1]};
}
}
}
return dp[m][n];
}
- Edit Distance
int minDistance(string word1, string word2)
{
int n = word1.length;
int m = word2.length;
int dp[n][m];
//边界条件
for (int i = 0; i < n + 1; i++)
dp[i][0] = i;
for (int j = 0; j > m + 1; j++)
dp[0][j] = j;
for (int i = 0; i < n + 1; i++)
{
for (int j = 0; j < m + 1; j++)
{
dp[i][j] = min{dp[i - 1][j], dp[i][j - 1]} + 1; //delet和insert中找最小的
if (word1.charAt(i - 1) == word2.charAt(j - 1))
{
dp[i][j] = min{dp[i][j], dp[i - 1][j - 1]};//如果最后一个元素相同,则找前一个和这个,谁最小
}
else
{
dp[i][j] = min{dp[i], dp[i - 1][j - 1] + 1};//如果不相同,则和replace进行比较
}
}
}
}
![IMG_1504(20201127-154444)](C:Users渐明DocumentsTencent Files834422139FileRecvMobileFileIMG_1504(20201127-154444).JPG)
算法思想:
用dp[i][0/1/2]分别表示第i天选择休息/健身/上网比赛的最少休息天数,因为不能连续两天做同样的事情,所以今天选择健身的话,从昨天选择了休息或者上网比赛的状态进行转移
#include <algorithm>
#include <stdio.h>
#include <stdlib.h>
using namespace std;
const int INF = 10000;
const int mx = 110;
int dp[mx][3];
int main()
{
int n, a;
scanf("%d", &n);
memset(dp, INF, sizeof(dp));
//dp[i][0/1/2] rest/gym/match
dp[0][0] = 0;
for (int i = 1; i <= n; i++)
{
scanf("%d", &a);
int minn = min(dp[i - 1][1], dp[i - 1][2]);
dp[i][0] = min(dp[i - 1][0], minn) + 1;
if (!a)
continue;
if (a != 2) //choose to join match
{
dp[i][2] = min(dp[i - 1][0], dp[i - 1][1]);
}
if (a != 1) //choose to gym
{
dp[i][1] = min(dp[i - 1][0], dp[i - 1][2]);
}
}
printf("%dn", min(dp[n][0], min(dp[n][1], dp[n][2])));
return 0;
}
3. 0-1背包问题
0-1背包的意思是:要么拿,要么不拿;拿只能拿一整个,不存在只是拿部分;物品只有一个,拿了之后就没了
//问题描述:
Input:
n items
v[i]: ith item 的价值
w[i]: ith item 的重量
C:knapsack 的最大承重量
Note: at most one copy of each item
Output: 能带走的最大价值总和
//问题分析:
f(i,j)=max{f(i+1,j),f(i+1,j-w[i])+v[i]} //第i个物品不选,第i个物品选,从中选出最大的一项出来
Base Case: f(0,j)=0 //房子内没有物品
f(i,0)=0 //没有带袋子/或者是袋子已经满了
public int knapsack(int &v,int &w,int C)
{
int n=v.length;
int[][] dp=new int[n+1][C+1];
for(int r=1;r<=n;r++)
{
for(int c=1;c<=C;c++)
{
dp[r][c]=dp[r-1][c]; //表示未放入这个商品之前,最优解
if(w[r-1]<=c)
{
dp[r][c]=Math.max(dp[r][c],dp[r-1][c-w[r-1]]+v[r-1]);
}
}
}
}
4. 完全背包问题
完全背包是在N种物品中选取若干件( 同一种物品可多次选取)放在空间为V的背包里,每种物品的体积为C1,C2,…,Cn,与之相对应的价值为W1,W2,…,Wn.求解怎么装物品可使背包里物品总价值最大。
状态转移方程: $$ dpleft[ i right] left[ j right] =max left{ dpleft[ i-1 right] left[ j-kcdot vleft[ i right] right] +kcdot vleft[ i right] right} ,, 0leqslant k&kcdot wleft[ i right] ll j $$
//问题描述:
Input:
n items
v[i]: ith item的价值
w[i]: ith item的重量
C:Knapsack的最大承重重量
Note: infinite copies of each item(每一种物品不限制数量)
Output: 能带走的最大价值总和
#include <iostream>
#include <string.h>
using namespace std;
#define Max_n 105
#define Max_w 10005
int n,W;
int w[Max_n],v[Max_n];
int dp[Max_n][Max_w];
//初始化
void initialize(int n,int m)
{
for(int i=0; i<=n; i++)
dp[i][0]=0;
for(int j=0; j<=W; j++)
dp[0][j]=0;
}
//dp[i][j]=max{dp[i-1][j-k*w[i]]+k*v[i]|0≤k&k*w[i]≤j}
void solve_1()
{
for(int i=1; i<=n; i++)
for(int j=1; j<=W; j++)
{
dp[i][j]=-1;
for(int k=0; k*w[i]<=j; k++)
dp[i][j]=max(dp[i][j],dp[i-1][j-k*w[i]]+k*v[i]);
}
}
注:完全背包和0-1背包的两个for循环的位置代表的东西不同,主要是由于i用来表示每一种物品。