【leetcode】面试题1:最短超串,详解滑动窗口

假设你有两个数组,一个长一个短,短的元素均不相同。找到长数组中包含短数组所有的元素的最短子数组,其出现顺序无关紧要。

返回最短子数组的左端点和右端点,如有多个满足条件的子数组,返回左端点最小的一个。若不存在,返回空数组。

示例 1:

输入:
big = [7,5,9,0,2,1,3,5,7,9,1,1,5,8,8,9,7]
small = [1,5,9]
输出: [7,10]

示例 2:

输入:
big = [1,2,3]
small = [4]
输出: []
提示:

big.length <= 100000
1 <= small.length <= 100000

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/shortest-supersequence-lcci
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

本题乍看之下我有两种思路

  • 滑动窗口:(我发现很多字符串的题都需要用到滑动窗口,下去深究一下KMP算法)设置两个指针
  • 重新定义一个hashmap,将big数组中找到的small数组中的数字都放到hashmap中,下标存value,值存key,然后找连续出现的几个值,比较value的差

我感觉我的这两组思想都可以行得通,但是实际实现起来就有点举步维艰,有其是遍历,我想把small数组成环遍历,但不知道遍历的终止条件;
hashmap中遍历和small数组比较也是个问题;

总结还是自己做的题太少了,
在评论区找了几种算法,记录一下;

  • 第一种
    1、将small数组中的数存在map中,其value值初始化为-1;
    2、遍历big数组,map存储small数组中每一个值在big数组中的位置,并更新;
    3、当在big数组中找齐了所有在small数组中的数字后,就用当前下标i减去map 中value的最小值(即位置的最小值)得到的差即为“包含短数组所有元素的子数组长度”

4、时间复杂度O(n*m)

  • 第二种
    滑动窗口
    当我们觉得程序无法进行下去的时候,可以多借助外力;
    比如new 一个map,多设置一个指针……
    我们纠结于窗口什么时候停止的时候,可以设置一个count来计数,当数量和small中的个数相同的时候窗口就不必扩大;

滑动窗口这个链接讲的非常详细

子串问题大都用滑动窗口
这个题目和“最小覆盖子串问题”挺像的(76题)
在这里插入图片描述
只不过这个是返回下标,换汤不换药,有种做数学题的感觉,题型是有数的,题目是无数的,归根结底是方法;

字符串匹配问题——滑动窗口

在写了76题(最小覆盖子串)后,我明白了;
所以理解一个算法还是要先写出来,带入数据在编译器中执行,看每一步每一步是怎么走的。

  public static String minWindow(String s,String t){
        //参数安全检测
       if(s==null||t==null||s.length()==0||t.length()==0){
           return null;
       }
       //字符串转为数组便于操作
        char[] tc = t.toCharArray();
        //新建两个数组来存放个数
        int []winSize=new int[128];//存放s字符串中各个字符出现的次数
        int []needSize=new int[128];//存放t字符串中各个字符出现的次数
        //这是我看视频解析的时候,官方的思路;这里非常巧妙,
        // 仅仅一个数组,就完成了hashMap的工作,用ASCII码和++共同控制key-value

        //将目标字符串t中的各个字符出现的次数先存放到数组中,方便之后的比较
        for(int i=0;i<t.length();i++){
            needSize[tc[i]]+=1;
        }

        int left=0,right=0;
        int count=0;//出现的次数指针
        int minLength=s.length()+1;//记录窗口大小的最小值;因为有可能出现多组,所以我们需要比较哪个长度最小才是最优解
        String res=new String();//记录结果

        //开始循环,以右窗口为退出条件
        while (right<s.length()){
            char ch=s.charAt(right);//直接用right指针,省去我们开辟一个s字符数组
            winSize[ch]++;//将s中各个字符出现的次数记录下来
            if(needSize[ch]>0&&needSize[ch]>=winSize[ch]){
                //这个条件乍一看挺难理解的
                //可是我们逆向思维推一下
                //如果winSize[ch]>needSize[ch]的值的时候,就会出现重复包含的情况(如人家只需要两个a,你这里有三个a)
                count++;
            }


            //如果当我们的窗口包含了t整个字符串
            while (count==t.length()){
                //考虑缩小窗口的同时维护count
                ch=s.charAt(left);
                if(needSize[ch]>0&&needSize[ch]>=winSize[ch]){
                    count--;
                }
                //维护最小窗口,如果当前窗口小于之前的窗口则更新
                if(right-left+1<minLength){
                    minLength=right-left+1;
                    res=s.substring(left,right+1);
                }
                winSize[ch]--;//维护winSize
                left++;//缩小串口
            }
            right++;
        }
        return res;
    }

这个题我觉得很妙的是用数组间接替代了hashMap,之前也看过这种思想,经常不用就忘了,还是要多看。

总结,写代码需要规范,就像数学的解答题步骤,上面的连接中总结了一个滑动窗口的算法框架,大家可以学习一下;不过我觉得最好的方法还是调试,一步一步看清楚明了。

根据上面那道题用相同的方法将面试题写出来了;
优化:可以将needSize改为set集合

 public static int[] shortestSeq(int[] big, int[] small) {

        if(big==null||small==null||big.length==0||small.length==0){
            return new int[0];
        }
        if(small.length>big.length){
            return new int[0];
        }

        HashMap<Integer,Integer>winSize=new HashMap<>();
        HashMap<Integer,Integer>needSize=new HashMap<>();

        for(int a:small){
            needSize.put(a,needSize.getOrDefault(a,0)+1);
        }
        int left=0,right=0;
        int count=0;
        int minLength=big.length+1;
        int []res=new int[2];
        while (right<big.length){
            winSize.put(big[right],winSize.getOrDefault(big[right],0)+1);
            if (needSize.containsKey(big[right])  && needSize.get(big[right]) >= winSize.get(big[right])) {
                count++;
            }

            while (count==small.length){
                if(needSize.containsKey(big[left])&&needSize.get(big[left])>=winSize.get(big[left])){
                    count--;
                }

                if(right-left+1<minLength){
                    minLength=right-left+1;
                    res[0]=left;
                    res[1]=right;
                }

                winSize.put(big[left],winSize.getOrDefault(big[left],0)-1);
                left++;
            }
            right++;

        }
        if(res[0]==res[1]&&res[0]==0){
            return new int[0];
        }
        return res;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值