相比矩阵类动态规划,序列类动态规划最大的不同在于,对于第 i 个位置的状态分析,它不仅仅需要考虑当前位置的状态,还需要考虑前面 i – 1 个位置的状态
最长上升子序列
解题思路:
- 问题拆解:求前n个数的最长子序列,可以先求前n-1个数的最长子序列的长度,但是这样不具有后效性(n用不上n-1的答案),可以求以n为终点的最长上升子序列的长度,这样n-1的问题解决了,n的问题也就解决了
- 状态定义为:从起点到第n个数的最长子序列
- 递推方程:将第n个与前n-1个数相比,如果大于前面数,则最长子序列长度+1,否则为1,maxLen[i]=max(maxLen[i],maxLen[j]+1)
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAXN=1010;
int a[MAXN];
int maxLen[MAXN];
int main(){
int N;cin>>N;
for(int i=1;i<=N;i++){
cin >>a[i];maxLen[i]=1;
}
for(int i=2;i<=N;i++){
for(int j=1;j<i;j++){
if(a[i]>a[j]){
//为什么不直接maxLen[i]=maxLen[j]+1;因为maxLen[i]可能要比maxLen[j]+1要大
maxLen[i]=max(maxLen[i],maxLen[j]+1);
}
}
}
cout << * max_element(maxLen+1,maxLen+N+1);
}
粉刷房子
假如有一排房子,共 n 个,每个房子可以被粉刷成红色、蓝色或者绿色这三种颜色中的一种,你需要粉刷所有的房子并且使其相邻的两个房子颜色不能相同。
当然,因为市场上不同颜色油漆的价格不同,所以房子粉刷成不同颜色的花费成本也是不同的。每个房子粉刷成不同颜色的花费是以一个 n x 3 的矩阵来表示的。
例如,costs[0][0]
表示第 0 号房子粉刷成红色的成本花费;costs[1][2]
表示第 1 号房子粉刷成绿色的花费,以此类推。请你计算出粉刷完所有房子最少的花费成本。
注意:
所有花费均为正整数。
- 问题拆解:你会发现刷第 i 个房子的花费其实是和前面 i – 1 个房子的花费以及选择相关,如果说我们需要知道第 i 个房子使用第 k 种油漆的最小花费,那么你其实可以思考第 i – 1 个房子如果不用该油漆的最小花费,这个最小花费是考虑从 0 到当前位置所有的房子的。
- 状态定义: dp[i][k],表示如果第 i 个房子选择第 k 个颜色,那么从 0 到 i 个房子的最小花费
- 递推方程:dp[i][k] = Math.min(dp[i - 1][l], ..., dp[i - 1][r]) + costs[i][k], l != k, r != k
- 为我们要考虑 i – 1 的情况,但是第 0 个房子并不存在 i – 1 的情况,因此我们可以把第 0 个房子的最小花费存在状态数组中,当然你也可以多开一格 dp 状态,其实都是一样的
#include <iostream>
#include <cstdio>
#include <algorithm>
int dp[10][3];
int costs[10][3];
using namespace std;
int findMin(int x,int y,int z){
int maxResult =min(x,y);
maxResult =min(maxResult,z);
return maxResult;
}
int main(){
int m;//墙数
cin >>m;
for(int i=0;i<m;i++){
for(int j=0;j<3;j++){
cin >>costs[i][j];
}
}
for(int i=0;i<3;i++){
dp[0][i]=costs[0][i];
}
for(int i=1;i<m;i++){
dp[i][0]=min(dp[i-1][1],dp[i-1][2])+costs[i][0];
dp[i][1]=min(dp[i-1][0],dp[i-1][2])+costs[i][1];
dp[i][2]=min(dp[i-1][0],dp[i-1][1])+costs[i][2];
}
cout <<findMin(dp[m-1][0],dp[m-1][1],dp[m-1][2]);
return 0;
}
打家劫舍
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额
-
问题拆解
我们要求解抢完 n 个房子所获得的最大收入,可以思考第 i 个房子是否应该抢,如果要抢,那么第 i – 1 个房子就不能抢,我们只能考虑抢第 i – 2 个房子。这样一来,第 i 个房子就和第 i – 1 个房子,以及第 i – 2 个房子联系上了。
-
状态定义
抢到当前房子可以获得的最大值其实是和抢到前两个房子可以获得的最大值有关,因此我们可以用 dp[i] 表示抢到第 i 个房子可以获得的最大值
-
递推方程
如果我们抢第 i 个房子,那么我们就只能去考虑第 i – 2 个房子,如果不抢,那么我们可以考虑第 i – 1 个房子,于是递推方程就有了:dp[i] = Math.max(dp[i - 2] + nums[i], dp[i - 1])
- 实现:初始值,dp[1] 就是最左边的房子获得的最大价值,这个房子之前也没有其他的房子,直接抢即可
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int home[20];
int money[20];
int rob(int home[],int N){
money[0]=0;//一个房子还没抢
money[1]=home[0];//最左边房子获得的最大价值
for(int i=2;i<=N;i++){
money[i]=max(money[i-2]+home[i-1],money[i-1]);
}
return money[N];
}
int main(){
int N;
cin >>N;
for (int i=0;i<N;i++){
cin >>home[i];
}
cout <<rob(home,N);//传入输入数组和数组长度
return 0;
}
打家劫舍II
你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。
示例 1:
输入: [2,3,2]
输出: 3
解释: 你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。
示例 2:
输入: [1,2,3,1]
输出: 4
解释: 你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
题目解析:
房子排列方式是一个圆圈意味着之前的最后一个房子和第一个房子之间产生了联系,这里有一个小技巧就是我们线性考虑 [0, n – 2] 和 [1, n – 1],然后求二者的最大值。
其实这么做的目的很明显,把第一个房子和最后一个房子分开来考虑。实现上面我们可以直接使用之前的实现代码。
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int home[20];
int money[20];
int rob(int home[],int N){
money[0]=0;//一个房子还没抢
money[1]=home[0];//最左边房子获得的最大价值
for(int i=2;i<=N;i++){
money[i]=max(money[i-2]+home[i-1],money[i-1]);
}
return money[N];
}
int rob2(int home[],int N){
int array1[N],array2[N];
for(int i=0;i<N-1;i++){
//0到n-2
array1[i]=home[i];
}
for(int i=1;i<N;i++){
//1到n-1
array2[i-1]=home[i];
}
return max(rob(array1,N-1),rob(array2,N-1));
}
int main(){
int N;
cin >>N;
for (int i=0;i<N;i++){
cin >>home[i];
}
cout <<rob2(home,N);//传入输入数组和数组长度
return 0;
}