LeetCode刷题 day05

一.字符串匹配

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思考与解析

  1. 整体代码封装成一个函数方法,返回String类型数据,传入参数str和最小长度限制m
  2. 考虑用两个循环实现截取子串的工作,一个负责起点,一个负责截取的终点位置。截取方法使用java内部的substring()函数,别的语言一般也都有。
  3. 统计出现的每个大于m的子串出现次数,考虑使用map映射表储存,key储存子串,vaule储存出现次数。
  4. j为起始位置,i代表截的长度,j+i就是截止的位置。
  5. 先初始化,如果所有子串只出现一次,默认的就是str本身,设置一个变量max和res,分别用来记录最大次数和对应的子串。
  6. 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问题介绍

大名鼎鼎的背包问题它来了,之前也学习过过背包问题,但是都是纸上谈兵,没有使用编程,实现过,今天就让我们来走进它的背后思想和逻辑。

  1. 在这里强烈安利一下,b站的这位博主的讲解:
    背包问题讲解,十六分钟,图文并茂,十分容易理解

  2. 问题要求如下
    我们先把它具体化:你有个背包,容量为8,在你前面有4件物品。
    给它们编号,体积和价值价值如下。问背包最多能装下多少价值的物品?

编号体积价值
123
234
345
456

2.2求最大价值

如图所示:
在这里插入图片描述
解析:
接下来就开始理解一下这张价格表的含义了。

  1. 首先设置一个0编号的物体,体积也为0,价值也为0,目的是为了动态规划的初始化。所以初始化,首行和首列都为0,这很好理解。
  2. 再设置两个数组,分别对应物品的体积和价值:v和p
  3. 这里我们令i表示第i行,j表示第j列,这个二维数组为pt。
  4. pt[i][j]的含义:在背包容量为j,且只有i件物品的情况下,可以装下的最大价值。嗯嗯,这一点很重要,有动态规划的味儿了。
    如pt[2][3]:表示容量为3时,在物品有1,2的情况下,能装下的最大价值。

动态规划:

  1. 如果在容量j时不装入第i件物品,那它的pt值就和没有第i件物品,容量为j的情况下能装下的最大价值。这一点不难理解:pt[i][j]=pt[i-1][j]。

这里我们以pt[1][1]为例,容量为1时,目前只有1号物品可以选择,但背包装不下1,所以没有装1号物品,价格和pt[0][1],在只有0件物品的情况下装的最大价值一样,都是0。
我们还可以观察第二列和第三列,第二列都装不下物品,和第一行第二列保持一致。

  1. 最关键的来了,当前背包的容量>=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. 刚刚的两种选择,我们比较取其中较大的那一个,不停的这样操作,知道二维数组遍历结束。

2.3装的物品是什么(回溯)

在价值最大的情况下,我们不仅需要知道,背包能装下的最大价值,还需要知道背包中到底装了些什么,这是一个回溯问题。

  1. 从二维数组的最右下角开始回溯,如果发现pt[i][j]=pt[i-1][j],我们由什么讲解可以知道,第i件物品没有放入背包。
  2. 那如果不相等,就知道背包放入了第i件物品。下面一步,和之前有些类似,放入是预留体积,不过这边是回溯,是预留价格。先把第i件物品的价格减去,剩余的价值为pt[i][j]-p[i],说明前i-1个物品的最佳组合价值为pt[i][j]-p[i],在i-1行找到该值。
  3. 依次类推,直到回溯到第一行,初始状态。

2.4代码实现

  1. 背包所能装下的最大价值
    刚刚分析的二维数组方法:
    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;
    }
  1. 装下的物品回溯
   //回溯算法:求解背包中的物品
    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;
    }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱笑的蓝孩子~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值