蓝桥杯算法合集: 蓝桥杯算法合集(终极完结版)
动态规划
动态规划一般是用来解决最优子结构或者是子问题最优性,这类问题常见有重叠子问题。比如像递归解决斐波那契数列、爬楼梯方案数等。会发现递归调用过程中存在重复计算。对调用过程进行记忆化也是dp的一种体现。
思路:
我自己写的话一般是用画图工具(也可以用excel),列出表格,从小范围根据递推基一步一步去推,进而发现状态转移方程。
用一个表(常见数组)来记录所有已解子问题的答案。不管该子问题以后是否被用到,只要他被计算过就将结果填入表中,也叫打表法。
一般步骤:
- 确定状态(一是般先确定dp数组所表示的意义)
- 找到状态转移方程
要找状态方程可以这样思考,当前面临一个状态,它有可能是从哪儿来的?dp问题通常要逆向思考达到当前这个状态的最后一步,即压死骆驼的最后一根稻草。
本阶段状态=(上一个阶段状态+上一个阶段决策)
状态转移方程要保证当前状态可以接到所有该转移状态的转移。
解法形式:
自顶向上:递归 如果有重叠子问题,带备忘录。比如递归求斐波那契数列还有爬楼梯方案数(这也是dp解法的一种形式,会发现使用带备忘录的递归通常可以ac)。
自底向下:递推
应用:
- 01背包
- 完全背包问题
- 最长上升子序列问题
- 最长公共子序列
总结有
- 序列专题:
- 背包专题:
刷题过程中的小知识点:
(1)子矩阵不一定要取自原矩阵的连续行和列
有些初学者可能会认为,子矩阵一定是如下图这种取自原矩阵连续行列的矩阵。
注意边界处理
原矩阵:
1 2 3
4 5 6
7 8 9
子矩阵:
1 2 3
4 5 6
其实子矩阵不一定取自原矩阵的连续行列,也可以是不连续行或者列:
1 2
4 5
或
1 3
4 6
7 9
(2)、子序列不一要求连续但是子串要连续
背包问题专题
背包问题常见以下几种
01背包
模型特点:每种物品只有一个,且要么选要么不选,有01两种状态因而以01得名。有限制范围,常见体积或者重量限制,一般用二维数组dp[i][j]表示状态,其中第一个维度表示可选范围,第二个维度表示容量等限制范围。
完全背包
模型特点:与01背包相似,唯一不同的是,完全背包的物品是由无限个。
01背包问题
/*
题目描述
给定N个物品,每个物品有一个重量W和一个价值V.你有一个能装M重量的背包.问怎么装使得所装价值最大.每个物品只有一个.
输入
输入的第一行包含两个整数n, m,分别表示物品的个数和背包能装重量。
以后N行每行两个数Wi和Vi,表示物品的重量和价值
输出
输出1行,包含一个整数,表示最大价值。
样例输入
3 5
2 3
3 5
4 7
样例输出
8
4 5
2 3
1 2
3 4
2 2
得到的dp矩阵:
0 0 3 3 3 3
0 2 3 5 5 5
0 2 3 5 6 7
0 2 3 5 6 7
*
*/
package 动态规划;
import java.util.Scanner;
public class _01背包问题_动态规划 {
public static void main(String[] args) {
Scanner reader=new Scanner(System.in);
int n=reader.nextInt();//物品个数
int m=reader.nextInt();//背包承重量
int[] w=new int[n];//存放每个物品的重量
int[] v=new int[n];//存放每个物品的价值
for(int i=0;i<n;i++) {
w[i]=reader.nextInt();
v[i]=reader.nextInt();
//dp[i][j]中第一个维度表示可选范围 第二个维度表示容量
}
int [][]dp=new int[n][m+1];
//初始化dp数组第一行
for(int i=0;i<=m;i++) {//w[0]<i 中的i刚好可以表示背包容量
if(w[0]<=i) {//放得下0号物品
dp[0][i]=v[0];
}
}
for(int i=1;i<n;i++) {
for(int j=0;j<=m;j++) {
if(w[i]<=j) {
int v1=dp[i-1][j];//不要当前这个物品
int v2=dp[i-1][j-w[i]]+v[i];//要当前这个物品
dp[i][j]=Math.max(v1, v2);
}else {
dp[i][j]=dp[i-1][j];
}
}
}
System.out.println(dp[n-1][m]);
// for(int i=0;i<n;i++) {
// for(int j=0;j<=m;j++) {
// System.out.print(dp[i][j]+" ");
// }
// System.out.println();
// }
完全背包问题
/*
有 N 种物品和一个容量是 V
的背包,每种物品都有无限件可用。
第 i种物品的体积是 vi,价值是 wi
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行两个整数,N,V
,用空格隔开,分别表示物品种数和背包容积。
接下来有 N
行,每行两个整数 vi,wi,用空格隔开,分别表示第 i
种物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤1000
0<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
10
链接:
https://www.acwing.com/problem/content/description/3/
*/
package 动态规划;
import java.util.Scanner;
public class 完全背包问题 {
public static void main(String[] args) {
Scanner reader=new Scanner(System.in);
int n=reader.nextInt();//物品个数
int m=reader.nextInt();//背包承重量
int[] w=new int[n];//存放每个物品的重量
int[] v=new int[n];//存放每个物品的价值
for(int i=0;i<n;i++) {
w[i]=reader.nextInt();
v[i]=reader.nextInt();
}
int[][] dp=new int[n][m+1];
//初始化dp数组第一行
for(int i=0;i<m;i++) {
if(w[0]<=i) {
dp[0][i]=v[0]+dp[0][i-w[0] ];
}
}
for(int i=1;i<n;i++) {//i表示行号 同时也是物品号
for(int j=1;j<=m;j++) {//j表示列好 同时也是背包容量
if(w[i]<=j) {
int v1=v[i]+dp[i][j-w[i]];//选当前物品
int v2=dp[i-1][j];
dp[i][j]=Math.max(v1,v2);
}else {
dp[i][j]=dp[i-1][j];
}
}
}
// for(int i=0;i<n;i++) {
// for(int j=1;j<=m;j++) {
// System.out.print(dp[i][j]+" ");
// }
// System.out.println();
// }
System.out.println(dp[n-1][m]);
}
}
}
}
序列专题
动态规划常见序列模型专题有以下几种:
最大子段问题
最长上升子序列
dp[i]表示以i号元素为结尾的最长上升子序列长度
最大公共子序列
dp[i][j]表示s1前i个元素和s2前j元素的公共子列长度
最大子段和问题
最大子段和就是所有子段中和最大的
例如:
-2 11 -4 13 - 5 -2
最大子段和就是:11+(-4)+13=20
package 常见动态规划模型;
/*
测试数据:
6
-2 11 -4 13 -5 -2
答案:
20
*/
import java.util.Scanner;
public class 最大子段和 {
static int INF=0x7fffffff;//16进制表示的最大整数
public static void main(String[] args) {
Scanner reader=new Scanner(System.in);
int n=reader.nextInt();
int []num=new int[n+1];
for(int i=0;i<n;i++) {
num[i]=reader.nextInt();
}
int ans=-INF;
for(int i=0;i<n;i++) {//如果序列全为非正数 最大子段和为最大值
ans=Math.max(ans, num[i]);
}
if(ans<=0) {//特判掉全为非正数的情况
System.out.println(ans);
}else{//序列有正也有负
int sum=0;
for(int i=0;i<n;i++) {
if(sum+num[i]<=0) {
sum=0;
}else {//以i号元素为结尾的序列有正向作用
sum=sum+num[i];
}
ans=Math.max(ans, sum);//看看是否需要更新ans
}
System.out.println(ans);
}
}
}
最长上升子序列
package 常见动态规划模型;
/*
测试数据:
6
3 2 6 1 4 5
答案:3
6
4 10 4 3 8 9
答案:3
*/
import java.util.Scanner;
public class 最长上升子序列 {
static int n;
static int []a;
static int []dp;
//dp[i]表示以i号元素结尾的最长上升子序列个数
//状态方程:dp[i]=max(dp[i],dp[j]+1) (1<=j<i && a[j]<a[i])
//时间复杂度:O(n^2)
static int LIS() {
int ans=0;
for(int i=1;i<=n;i++){
dp[i]=1;//初值
for(int j=1;j<i;j++) {
if(a[j]<a[i]) {//更新为较大者
dp[i]=Math.max(dp[i], dp[j]+1);
}
}
ans=Math.max(ans, dp[i]);
}
return ans;
}
public static void main(String[] args) {
Scanner reader=new Scanner(System.in);
n=reader.nextInt();
dp=new int[n+1];
a=new int[n+1];
for(int i=1;i<=n;i++) {
a[i]=reader.nextInt();
}
System.out.println(LIS());
}
}
最长公共子序列
dp表
再举个例子
package 动态规划;
public class 最长公共子序列 {
/**递归
* @param nums1
* @param i 表示nums1数组的前i个
* @param nums2
* @param j 表示nums2数组的前j个
* @return 最长公共子序列的长度
* 空间复杂度:O(Math.min(m,n) 递归深度*辅助空间
* 时间复杂度:O(2^n)
*/
int LongestCommonSubsequence0(String s1,int i,String s2,int j) {
if(i==0||j==0||s1==null||s2==null)return 0;
if(s1.charAt(i-1)==s2.charAt(j-1)) {
return LongestCommonSubsequence0(s1,i-1,s2,j-1)+1;
}
//返回nums1前i个与nums2前j-1个 和 nums1前i-1个与nums2前j个最长公共子序列的较大者
return Math.max(LongestCommonSubsequence0(s1,i,s2,j-1 ),
LongestCommonSubsequence0( s1,i-1,s2,j ) );
}
//动态规划解法
/**确定状态 dp[i][j] 表示s1的前i个字符与s2的前j个字符的公共子序列长度
* 递推基 dp[0][j] 和 dp[i][0]都为0
* @param s1
* @param s2
* @return dp[len1][len2]即为s1和s2的最大公共子序列长度
* 空间复杂度:O(m*n)
* 时间复杂度:O(m*n)
*
*/
int LongestCommonSubsequence1(String s1,String s2) {
if(s1==null||s2==null)return 0;
int m=s1.length();
int n=s2.length();
int [][]dp=new int [m+1][n+1];
for(int i=1;i<=m;i++) {//枚举每一行
for(int j=1;j<=n;j++) {
if(s1.charAt(i-1)==s2.charAt(j-1)) {
dp[i][j]=dp[i-1][j-1]+1;
}else {
dp[i][j]=Math.max(dp[i-1][j], dp[i][j-1]);
}
}
}
return dp[m][n];
}
/**
* 优化空间复杂度为O(2*(m或n))
* 使用两行的滚动数组存储状态
* 字符串为空即 "" 存在对象但length为0
* 与字符串等于null不一样 为null表示该对象不存在 不能调用字符串的方法
* @param s1
* @param s2
* @return
*/
int LongestCommonSubsequence2(String s1,String s2) {
if(s1==null||s2==null)return 0;
int m=s1.length();
int n=s2.length();
if(m==0||n==0) return 0;
int [][]dp=new int [2][n+1];
for(int i=1;i<=m;i++) {
for(int j=1;j<=n;j++) {
if(s1.charAt(i-1)==s2.charAt(j-1)) {
dp[i%2][j]=dp[(i-1)%2][j-1]+1;
}else {
dp[i%2][j]=Math.max(dp[(i-1)%2][j], dp[i%2][j-1]);
}
}
}
return dp[m%2][n];
}
int LongestCommonSubsequence3(String s1,String s2) {
if(s1==null||s2==null)return 0;
int m=s1.length();
int n=s2.length();
if(m==0||n==0) return 0;
int []dp=new int[n+1];
for(int i=1;i<=m;i++) {
int cur=0;
for(int j=1;j<=n;j++) {
int lefttop=cur;
cur=dp[j];
if(s1.charAt(i-1)==s2.charAt(j-1)) {
dp[j]=lefttop+1;
}else {
dp[j]=Math.max(dp[j-1], dp[j]);
}
}
}
return dp[n];
}
/**
* 使用一维数组 包含上一行部分dp值和本行部分dp值
* @param s1
* @param s2
* @return
*/
int LongestCommonSubsequence4(String s1,String s2) {
if(s1==null||s2==null)return 0;
int m=s1.length();
int n=s2.length();
if(m==0||n==0) return 0;
int []dp=new int[n+1];
for(int i=1;i<=m;i++) {
int lefttop=0;//换到下一行之后要清零
for(int j=1;j<=n;j++) {
if(s1.charAt(i-1)==s2.charAt(j-1)) {
//覆盖前先保存dp[j] 该值为下一个的左上
int now=dp[j];
dp[j]=lefttop+1;
lefttop=now;
}else {
lefttop=dp[j];
dp[j]=Math.max(dp[j-1], dp[j]);
}
}
}
return dp[n];
}
/**
* 以长度较小者作为dp的长度
* @param s1
* @param s2
* @return
*/
int LongestCommonSubsequence5(String s1,String s2) {
if(s1==null||s2==null)return 0;
if(s1.length()==0||s2.length()==0) return 0;
String rowNums,colNums;
//s1长度比s2大
if(s1.length()>s2.length()) {
rowNums=s1;
colNums=s2;
}else {
rowNums=s2;
colNums=s1;
}
int []dp=new int[colNums.length()+1];
for(int i=1;i<=rowNums.length();i++) {
int cur=0;
for(int j=1;j<=colNums.length();j++) {
int lefttop=cur;
cur=dp[j];
if(rowNums.charAt(i-1)==colNums.charAt(j-1)) {
dp[j]=lefttop+1;
}else {
dp[j]=Math.max(dp[j-1], dp[j]);
}
}
}
return dp[colNums.length()];
}
public static void main(String[] args) {
String s1="BA34C";
String s2="A1BC2";
int len1=s1.length();
int len2=s2.length();
最长公共子序列 a=new 最长公共子序列();
System.out.println(a.LongestCommonSubsequence0(s1,len1,s2,len2));
System.out.println(a.LongestCommonSubsequence1(s1,s2));
System.out.println(a.LongestCommonSubsequence2(s1,s2));
System.out.println(a.LongestCommonSubsequence3(s1,s2));
System.out.println(a.LongestCommonSubsequence4(s1,s2));
System.out.println(a.LongestCommonSubsequence5(s1,s2));
}
}