假设你有两个数组,一个长一个短,短的元素均不相同。找到长数组中包含短数组所有的元素的最短子数组,其出现顺序无关紧要。
返回最短子数组的左端点和右端点,如有多个满足条件的子数组,返回左端点最小的一个。若不存在,返回空数组。
示例 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;
}