滑动窗口
3. 无重复字符的最长子串
题解:
-
最初采用的是哈希表加双重循环遍历从每个索引开始的最长字符串, 但这样的时间空间复杂度都特别高, 仔细观察有可以优化的地方
-
改双层循环为单层循环, 不再遍历从每个索引开始的最长字符串, 观察可得当遇到与前面字符串重复的字符#时, 从前面重复的字符#处开始即可
-
public int lengthOfLongestSubstring(String s) { if(s.length() == 0 || s==null){ return 0; } Map<Character, Integer> map = new HashMap<>(); int max = 0; for(int i = 0, j = 0; j < s.length(); j++){ while(map.containsKey(s.charAt(j))){ map.remove(s.charAt(i)); i++; } map.put(s.charAt(j),j); max = Math.max(max,j-i+1); // for(int j = i + 1; j < s.length(); j++){ // if(map.containsKey(s.charAt(j))){ // i = map.get(s.charAt(j)); // break; // }else{ // map.put(s.charAt(j), j); // temp++; // } // } } return max; }
438. 找到字符串中所有字母异位词
题解:
-
查找异位词, 如果用哈希表比较麻烦, 因为涉及该字符串出现的随机顺序,
-
由于出现的只会是小写字符, 那么可以通过$-'a’来将字符判断转换为在int[26]的数组里判断出现次数
-
首先判断两数组头部字符串是否相等–通过获取字符串第i个位置字符串-'a’的索引即可知道该字符出现频率
-
然后在长度为s.length-p.length范围内不断以1单位滑动窗口即可
-
public List<Integer> findAnagrams(String s, String p) { List<Integer> res = new ArrayList<>(); int sLen = s.length(); int pLen = p.length(); if (sLen < pLen) { return new ArrayList<>(); } int[] sCount = new int[26]; int[] pCount = new int[26]; for (int i = 0; i < pLen; i++) { sCount[s.charAt(i) - 'a']++; pCount[p.charAt(i) - 'a']++; } if(Arrays.equals(sCount, pCount)){ res.add(0); } for(int i = 0; i<sLen-pLen;i++){ sCount[s.charAt(i) - 'a']--; sCount[s.charAt(i+pLen) - 'a']++; if(Arrays.equals(sCount, pCount)){ res.add(i+1); } } return res; }
子串
560. 和为 K 的子数组
题解:
-
方法一循环遍历每一个索引,计算子串即可, 时间复杂度O(n^2)
-
方法二, 使用前缀和, 一次遍历记录数组头到索引i的子串和, 并使用哈希表来存储该值
-
那么每到一个索引, 若该位置的前缀和p[j] 与前面位置某一索引i的前缀和p[i]只差为k, 那就是从i+1~j的子串和为k
-
采用哈希表来存储该位置的前缀和, key为前缀和, 由于数组的值可以是正数也可是负数, 会存在多个位置的前缀和为同一值, 那么value可以记录为该前缀和一共记录的次数
-
每遍历到一索引, 若该前缀和p[j]-k的值p[temp]存在于哈希表中, 那么子串的个数即为map.get(pre-k)个.
-
public int subarraySum(int[] nums, int k) { // int count = 0,sum=0; // for(int i=0;i<nums.length;i++){ // sum = 0; // for(int j = i;j<nums.length;j++){ // sum += nums[j]; // if(sum == k){ // count++; // } // } // } // return count; int count = 0,sum=0; Map<Integer,Integer> map = new HashMap<>(); map.put(0,1); for(int i = 0;i<nums.length;i++){ sum+=nums[i]; if(map.containsKey(sum-k)){ count+=map.get(sum-k); } map.put(sum,map.getOrDefault(sum,0)+1); } return count; }
239. 滑动窗口最大值
题解:
-
暴力解法在数组以及k特别大时会运行超时
-
通过暴力解法观察, 其实可以在k次比较最大值的地方优化, 如果新加入队列的值比队尾的值大, 那么此时该窗口的最大值就一定不是队尾的值了(至少都是新加入队列的值),
-
所以在元素加入队列时可以进行判断, 如果当前值>队尾元素, 移除队尾元素, 加入当前值; 如果当前值< 队尾元素, 仍需加入当前值(如果不加入当前值, 经过一段时间的滑动, 无法保证队尾元素在窗口中)
-
当需要判断当前窗口的最大值时, 从队列头获取即可, 通过队列存储的索引值来判断当前索引是否在窗口中,不在则移除直到找到元素
-
如何保证靠近队头的元素即窗口中最大值–窗口中最大值>不在窗口中的队尾元素时会移除队尾元素, 窗口中最大值<不在窗口中的队尾元素时会直接添加至队尾;
-
窗口中最大值>在窗口中的队尾元素时, 也会移除队尾元素, 窗口中非最大值遇到前方的窗口最大值时会添加在队尾
-
public int[] maxSlidingWindow(int[] nums, int k) { int n = nums.length; Deque<Integer> deque = new LinkedList<>(); for(int i = 0;i<k;i++){ while(!deque.isEmpty()&&nums[i]>=nums[deque.peekLast()]){ deque.pollLast(); } deque.offerLast(i); } int[] res = new int[n-k+1]; res[0] = nums[deque.peekFirst()]; for(int i = k;i<n;i++){ while(!deque.isEmpty()&&nums[i]>=nums[deque.peekLast()]){ deque.pollLast(); } deque.offerLast(i); while(deque.peekFirst()<=i-k){ deque.pollFirst(); } res[i-k+1] = nums[deque.peekFirst()]; } return res; }
76. 最小覆盖子串
题解:
-
用一个哈希表*HashMap<Character, Integer>*来记录滑动窗口需要的n字符的数量,对于该哈希表的值,可以分为以下三种情况:
- HashMap<Character, Integer>
- Integer<0 -> 滑动窗口还需要字符的数量
- Integer==0 滑动窗口刚好包括字符的数量
- Integer>0 滑动窗口超过n字符的数量
-
用一个变量V来记录当前已组成的字符串长度, 右指针开始移动,当遇到哈希表中存在的字符时:
- 如果该字符在哈希表中的值小于0,说明是滑动窗口缺少的字符,V++
- 递增哈希表的值
-
当V==字符串t的长度时,代表该滑动窗口包含了t字符串的所有字符(这时候可以发现哈希表中的所有值都是大于等于0的),右指针停止移动。记录此时左右指针的差值,就是当前滑动窗口的长度,取最小值。
-
此时,左指针开始移动,争取使滑动窗口长度变得更小,当遇到哈希表中存在的字符时:
- 递减哈希表的值
- 如果该字符在哈希表中的值小于0,说明滑动窗口缺少了当前左指针对应的字符,V–
-
public String minWindow(String s, String t) { if (s.length() < t.length()) { return ""; } HashMap<Character, Integer> count = new HashMap<>(); // 统计组成t字符串的每个字符数量 // count[n]<0:滑动窗口缺少多少个n字符 // count[n]==0:滑动窗口刚好包含多少个n字符 // count[n]>0:滑动窗口超过多少个n字符 for (char c : t.toCharArray()) { count.put(c, count.getOrDefault(c, 0) - 1); } int formed = 0; // 已形成的字符数量 int start = 0; // 记录最小覆盖子串的起始位置 int length = Integer.MAX_VALUE; // 记录最小覆盖子串的长度 for (int left = 0, right = 0, required = t.length(); right < s.length(); right++) { char c = s.charAt(right); // 更新窗口中的字符计数 if (count.containsKey(c)) { if (count.get(c) < 0) { formed++; } count.put(c, count.get(c) + 1); } // 当窗口中的字符满足条件时,尝试缩小窗口 while (formed == required) { if (right - left + 1 < length) { start = left; length = right - left + 1; } char d = s.charAt(left); left++; if (count.containsKey(d)) { count.put(d, count.get(d) - 1); if (count.get(d) < 0) { formed--; } } } } return length == Integer.MAX_VALUE ? "" : s.substring(start, start + length); }