一.字符串匹配
1.1题目要求
这一道题是蓝桥杯的题目,要求返回长度大于m的出现最多次数的子串,其中如果次数相同,长度更长的为输出值,如果长度也相同,先出现的为输出结果。
示例:
输入:
String str=“aabbaaaaba”;
int m=2;
输出结果:aa
解析:因为要求输出子串长度大于等于2,而aa出现了4次,出现次数最多。
示例:
输入:
String str=“abcabcdddd”;
int m=2;
输出:abc
解析:abc和dd都出现了两次,但是abc长度更长。
1.2思考与解析
- 整体代码封装成一个函数方法,返回String类型数据,传入参数str和最小长度限制m
- 考虑用两个循环实现截取子串的工作,一个负责起点,一个负责截取的终点位置。截取方法使用java内部的substring()函数,别的语言一般也都有。
- 统计出现的每个大于m的子串出现次数,考虑使用map映射表储存,key储存子串,vaule储存出现次数。
- j为起始位置,i代表截的长度,j+i就是截止的位置。
- 先初始化,如果所有子串只出现一次,默认的就是str本身,设置一个变量max和res,分别用来记录最大次数和对应的子串。
- if判断,如果map集合中有,将里面的次数加一,并更新res和max,没有就添加到集合中,value为1。
1.3代码编写
public static String MaxSub(String a,int m){
int n=a.length();
//默认答案为a
String res=a;
Map<String,Integer> map=new HashMap<>();
int max=0;
//i为起始位置
//j为截止位置
for(int i=m;i<=n;i++){
for(int j=0;j<=n-i;j++){
String substr=a.substring(j,j+i);
//map集合中有没有该元素
if (map.containsKey(substr)){
//获取当前出现的次数,再+1, 并更新集合中的对应次数
int count=map.get(substr);
count++;
map.replace(substr,count);
if (count>max){
//更新最大出现次数,以及对应的字符串
max=count;
res=substr;
}
//出现次数相同,取长度长的那个
else if(count==max){
// 因为是从前往后遍历的,出现早的优先
res=res.length()>=substr.length()?res:substr;
}
}else {
//没有,就将其放入集合中
map.put(substr,1);
}
}
}
return res;
}
二.背包问题(dp)
2.1问题介绍
大名鼎鼎的背包问题它来了,之前也学习过过背包问题,但是都是纸上谈兵,没有使用编程,实现过,今天就让我们来走进它的背后思想和逻辑。
-
在这里强烈安利一下,b站的这位博主的讲解:
背包问题讲解,十六分钟,图文并茂,十分容易理解 -
问题要求如下
我们先把它具体化:你有个背包,容量为8,在你前面有4件物品。
给它们编号,体积和价值价值如下。问背包最多能装下多少价值的物品?
编号 | 体积 | 价值 |
---|---|---|
1 | 2 | 3 |
2 | 3 | 4 |
3 | 4 | 5 |
4 | 5 | 6 |
2.2求最大价值
如图所示:
解析:
接下来就开始理解一下这张价格表的含义了。
- 首先设置一个0编号的物体,体积也为0,价值也为0,目的是为了动态规划的初始化。所以初始化,首行和首列都为0,这很好理解。
- 再设置两个数组,分别对应物品的体积和价值:v和p
- 这里我们令i表示第i行,j表示第j列,这个二维数组为pt。
- pt[i][j]的含义:在背包容量为j,且只有i件物品的情况下,可以装下的最大价值。嗯嗯,这一点很重要,有动态规划的味儿了。
如pt[2][3]:表示容量为3时,在物品有1,2的情况下,能装下的最大价值。
动态规划:
- 如果在容量j时不装入第i件物品,那它的pt值就和没有第i件物品,容量为j的情况下能装下的最大价值。这一点不难理解:pt[i][j]=pt[i-1][j]。
这里我们以pt[1][1]为例,容量为1时,目前只有1号物品可以选择,但背包装不下1,所以没有装1号物品,价格和pt[0][1],在只有0件物品的情况下装的最大价值一样,都是0。
我们还可以观察第二列和第三列,第二列都装不下物品,和第一行第二列保持一致。
- 最关键的来了,当前背包的容量>=V[i],说明背包能够装下这一行表示的物品,有两种选择:
(1)不装第i件物品,回到上面的情况,pt[i][j]=pt[i-1][j],这个很好理解
(2)选择装第i件物品,先给i预留空间V[i],剩余容量为j-V[i]。然后就是充分利用剩下的容量,找容量为j-V[i],并且物品在i-1个物品的情况下,背包能够装下的最大价值,也就是回到上一行找到pt[i-1][j-V[i]]。所以pt[i][j]=p[i]+pt[i-1][j-V[i]],其中p[i]为第i件物品的价值。 - 刚刚的两种选择,我们比较取其中较大的那一个,不停的这样操作,知道二维数组遍历结束。
2.3装的物品是什么(回溯)
在价值最大的情况下,我们不仅需要知道,背包能装下的最大价值,还需要知道背包中到底装了些什么,这是一个回溯问题。
- 从二维数组的最右下角开始回溯,如果发现pt[i][j]=pt[i-1][j],我们由什么讲解可以知道,第i件物品没有放入背包。
- 那如果不相等,就知道背包放入了第i件物品。下面一步,和之前有些类似,放入是预留体积,不过这边是回溯,是预留价格。先把第i件物品的价格减去,剩余的价值为pt[i][j]-p[i],说明前i-1个物品的最佳组合价值为pt[i][j]-p[i],在i-1行找到该值。
- 依次类推,直到回溯到第一行,初始状态。
2.4代码实现
- 背包所能装下的最大价值
刚刚分析的二维数组方法:
public static int MaxPrice(int []v, int []p, int c){
int n = v.length; // 记录物品的数量
if (n == 0) {
return 0;
}
// 横和列数要加1,因为要初始化,0件物品
int dp[][] = new int[n + 1][c + 1];
// 初始化第一行和第一列
for (int i = 0; i <= c; i++){
dp[0][i] = 0;
}
for (int i = 0; i <= n; i++){
dp[i][0] = 0;
}
// 核心算法,从dp[1][1]开始
// i代表物品编号,j表示当前背包的容量
for(int i = 1; i <= n; i++){
for (int j = 1; j <= c; j++){
// 可以装下物品,有两种选择:装或者不装,求其中的最大值
if (j >= v[i-1]){
// 装该物品,所获得的最大价值
// 注意这里的下标,因为我们默认的添加了0编号的物品,所以要-1
int p1 = p[i-1] + dp[i-1][j-v[i-1]];
// 不装,获得价值和上一行对应一样
int p2 = dp[i-1][j];
dp[i][j] = Math.max(p1, p2);
} else {
// 装不下该物品
dp[i][j] = dp[i-1][j];
}
}
}
// 返回最右下角的数据
return dp[n][c];
}
第二种:递归方法,自上而下的,已经得到了最后的值往前递归,效率很低
public static int MaxPrice2(int []v, int []p,int index,int C){
//边界条件
if (index<0 || C<=0){
return 0;
}
//不装该物品,状态
int res=MaxPrice2(v,p,index-1,C);
//装该物品,求两者最大的
if(v[index]<=C){
res=Math.max(res,p[index]+MaxPrice2(v,p,index-1,C-v[index]));
}
return res;
}
- 装下的物品回溯
//回溯算法:求解背包中的物品
public static int[] arr(int[][] dp,int[]v,int[]p) {
int n = dp.length - 1; // 物品的数量
int c = dp[0].length - 1; // 背包的容量
//动态数组
List<Integer> itemList = new ArrayList<>(); // 用于存储装入背包的物品索引
// 从最后一个状态开始回溯
int i = n;
int j = c;
while (i > 0 && j > 0) {
// 当前位置的值与前一行相同,说明该物品没有被选中
if (dp[i][j] == dp[i - 1][j]) {
i--;
}
// 当前位置的值与前一行不同,说明该物品被选中
else {
itemList.add(i - 1); // 添加物品的索引(注意要减去默认添加的0编号物品)
j -= v[i - 1]; // 背包容量减去选中的物品重量
i--;
}
}
// 将物品索引转换为数组
int[] obj = new int[itemList.size()];
for (int k = 0; k < itemList.size(); k++) {
obj[k] = itemList.get(itemList.size() - 1 - k); // 反转索引顺序,使其与原始物品顺序一致
}
return obj;
}