Java-数据结构-滑动窗口

一. 滑动窗口的简单介绍

        滑动窗口是双指针技巧的一种,常用于解决子串、子序列问题。滑动窗口的思想是维护一个窗口,不断滑动更新。滑动窗口的难点是各种细节:如何向窗口中添加元素、如何缩小窗口、何时更新结果。

        滑动窗口有一套通用的框架,解决滑动窗口题目大家都可以尝试套用该框架。框架的整体思路是移动窗口右边界,向窗口中添加元素,窗口满足要求解的问题(如窗口等于目标子串),开始滑动左边界找到满足条件的最小值。

        图来自【2】(参考来源)

 

 

二. 滑动窗口的代码模板&要点

代码模板

 
  1. public void slidingWindow(String str,String target){
  2. //用哈希表存储当前窗口中每个字符出现次数
  3. // need:存储待查找子串每个字符出现次数
  4. Map<Character,Integer> need,window = new HashMap();
  5. //窗口左右边界
  6. int left = 0;
  7. int right = 0;
  8. while(right<target.length()){
  9. char c1 = str.charAt(right) //待加入窗口的字符
  10. right++; //右滑窗口
  11. //更新窗口信息,即哈希表状态
  12. //满足窗口左滑条件
  13. while(){
  14. char c2 = s.charAt(left);
  15. left++;
  16. //更新窗口信息,即哈希表状态
  17. }
  18. }
  19. }
 

要点

(1)窗口的左右边界

        left和right分别负责滑动窗口的左右边界,那么有时这个窗口是固定的,有时则是不固定长度,会需要求最小长度。

(2)什么时候right++

        基本在窗口内,right是每轮都需要++的,可以通过continue提前跳过本轮,减少一丢时间

(3)什么时候left++

        在达到一定的条件后就需要收缩窗口了,若是固定窗口长度,泽到达固定长度后收缩,若是求最小长度,则一直收缩到窗口内的值不满足条件为止

(4)什么时候更新res

        res最后的返回答案,可以在每轮right++后进行更新,也可以在left++后更新,那么有时最后一轮right<nums.length时跳出循环,注意此时是否还需要一轮比较

 

 

三. leetcode实战

 

1. leetcode3 无重复字符的最长子串

        给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。

输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
     请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。

Boolean数组

 
  1. class Solution {
  2. public int lengthOfLongestSubstring(String s) {
  3. if(s == null){
  4. return 0;
  5. }
  6. boolean[] arr = new boolean[128];
  7. char[] ch = s.toCharArray();
  8. int left = 0;
  9. int right = 0;
  10. int dis = 0;
  11. int max =0;
  12. while(right < ch.length){
  13. if(arr[ch[right]] == false){
  14. arr[ch[right]] = true;
  15. right++;
  16. continue;
  17. }
  18. dis = right-left;
  19. max = max > dis? max : dis;
  20. while(arr[ch[right]] == true){
  21. arr[ch[left]] = false;
  22. left++;
  23. }
  24. }
  25. dis = right-left;
  26. max = max > dis? max : dis;
  27. return max;
  28. }
  29. }
 

HashMap

 
  1. class Solution {
  2. public int lengthOfLongestSubstring(String s) {
  3. if(s == null || s.length() == 0){
  4. return 0;
  5. }
  6. HashMap<Character,Integer> map = new HashMap<>();
  7. int left = 0;
  8. int right = 0;
  9. int max = 0;
  10. while(right < s.length()){
  11. char c = s.charAt(right);
  12. map.put(c,map.getOrDefault(c,0)+1);
  13. while(map.get(c) > 1){
  14. char leftc = s.charAt(left);
  15. map.put(leftc,map.get(leftc)-1);
  16. left++;
  17. }
  18. max = Math.max(max,right-left+1);
  19. right++;
  20. }
  21. return max;
  22. }
  23. }
 

int数组

 
  1. class Solution {
  2. public int lengthOfLongestSubstring(String s) {
  3. int left = 0;
  4. int right = 0;
  5. int diff = 0;
  6. int max = 0;
  7. int len = s.length();
  8. int[] nums = new int[256];
  9. while(right < len){
  10. if(nums[s.charAt(right)] == 0){
  11. nums[s.charAt(right)]++;
  12. right++;
  13. continue;
  14. }
  15. diff = right-left;
  16. max = Math.max(max, diff);
  17. while(nums[s.charAt(right)] > 0){
  18. nums[s.charAt(left)]--;
  19. left++;
  20. }
  21. }
  22. diff = right-left;
  23. max = Math.max(max, diff);
  24. return max;
  25. }
  26. }
 

本题小结:(1)注意在最后还有一次比较

                  (2)对于伪HashSet可以用boolean数组代替

                  (3)一般达到条件后left左移都是用while循环,达到一个条件后退出

 

2. leetcode438 找到字符串中所有字母异位词

        给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。

输入: s = "cbaebabacd", p = "abc"
输出: [0,6]
解释:
起始索引等于 0 的子串是 "cba", 它是 "abc" 的异位词。
起始索引等于 6 的子串是 "bac", 它是 "abc" 的异位词。 

 
  1. class Solution {
  2. public List<Integer> findAnagrams(String s, String p) {
  3. int[] ori = new int[26];
  4. int[] win = new int[26];
  5. int left = 0;
  6. int right = 0;
  7. List<Integer> list = new ArrayList<>();
  8. if(s.length() < p.length()) return list;
  9. for(int i = 0; i < p.length(); i++){
  10. ori[p.charAt(i)-'a']++;
  11. }
  12. int len = p.length();
  13. while(right < s.length()){
  14. char c = s.charAt(right);
  15. win[c-'a']++;
  16. while(win[c-'a'] > ori[c-'a']){
  17. win[s.charAt(left)-'a']--;
  18. left++;
  19. }
  20. if(right - left +1 == len){
  21. list.add(left);
  22. }
  23. right++;
  24. }
  25. return list;
  26. }
  27. }
 

本题小结:(1)注意边界位置right - left +1

                  

3. leetcode567 字符串的排列

给你两个字符串 s1 和 s2 ,写一个函数来判断 s2 是否包含 s1 的排列。如果是,返回 true ;否则,返回 false 。

换句话说,s1 的排列之一是 s2 的 子串 。

输入:s1 = "ab" s2 = "eidbaooo"
输出:true
解释:s2 包含 s1 的排列之一 ("ba").

 
  1. class Solution {
  2. public boolean checkInclusion(String s1, String s2) {
  3. if(s1.length() > s2.length()) return false;
  4. int[] oristr = new int[26];
  5. int[] win = new int[26];
  6. int left = 0;
  7. int right = 0;
  8. for(int i = 0; i < s1.length(); i++){
  9. oristr[s1.charAt(i)-'a']++;
  10. }
  11. while(right < s2.length()){
  12. char c1 = s2.charAt(right);
  13. win[c1 -'a']++;
  14. while( win[c1 -'a'] > oristr[c1 -'a']){
  15. char c2 = s2.charAt(left);
  16. win[c2-'a']--;
  17. left++;
  18. }
  19. if( right - left +1== s1.length()){
  20. return true;
  21. }
  22. right++;
  23. }
  24. return false;
  25. }
  26. }
 

 

4. leetcode76 最小覆盖子串

给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。
注意:
对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
如果 s 中存在这样的子串,我们保证它是唯一的答案。

输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"

 
  1. class Solution {
  2. public String minWindow(String s, String t) {
  3. if(s.length() < t.length()) return "";
  4. int[] ori = new int[128];
  5. int[] win = new int[128];
  6. int right = 0;
  7. int left = 0;
  8. int res = s.length();
  9. int count = 0;
  10. String str = "";
  11. for(int i = 0; i < t.length(); i++){
  12. ori[t.charAt(i)]++;
  13. }
  14. while(right < s.length()){
  15. char c = s.charAt(right);
  16. win[c]++;
  17. if(ori[c] >0 && win[c] <= ori[c]){
  18. count++;
  19. }
  20. while(count == t.length()){
  21. char cL = s.charAt(left);
  22. if(ori[cL] > 0 && win[cL] <= ori[cL]){
  23. count--;
  24. }
  25. if(right-left+1 <= res){
  26. res = right-left+1;
  27. str = s.substring(left,right+1);
  28. }
  29. left++;
  30. win[cL]--;
  31. }
  32. right++;
  33. }
  34. return str;
  35. }
  36. }
 

本题小结:(1)以count来记录在滑动窗口内有效字符串个数,对其他无效的不给记录

 

 

5. leetcode219 存在重复元素 II

        给你一个整数数组 nums 和一个整数 k ,判断数组中是否存在两个 不同的索引 i 和 j ,满足 nums[i] == nums[j] 且 abs(i - j) <= k 。如果存在,返回 true ;否则,返回 false 。

输入:nums = [1,2,3,1], k = 3
输出:true


HashMap

 
  1. class Solution {
  2. public boolean containsNearbyDuplicate(int[] nums, int k) {
  3. HashMap<Integer,Integer> map = new HashMap<>();
  4. int left = 0;
  5. int right = 0;
  6. while(right < nums.length){
  7. map.put(nums[right],map.getOrDefault(nums[right],0)+1);
  8. // map.put(c,map.getOrDefault(c,0)+1);
  9. if(map.get(nums[right]) >= 2){
  10. return true;
  11. }
  12. if(right - left == k){
  13. map.put(nums[left],map.get(nums[left])-1);
  14. left++;
  15. }
  16. right++;
  17. }
  18. return false;
  19. }
  20. }
 

HashSet

 
  1. class Solution {
  2. public boolean containsNearbyDuplicate(int[] nums, int k) {
  3. HashSet<Integer> set = new HashSet<>();
  4. for(int i = 0; i < nums.length; i++) {
  5. if(set.contains(nums[i])) {
  6. return true;
  7. }
  8. set.add(nums[i]);
  9. if(set.size() > k) {
  10. set.remove(nums[i - k]);
  11. }
  12. }
  13. return false;
  14. }
  15. }
 

本题小结:(1)以HashSet的长度充当k的长度,大大简化空间和时间。

6. leetcode220 存在重复元素 III

给你一个整数数组 nums 和两个整数 k 和 t 。请你判断是否存在 两个不同下标 i 和 j,使得 abs(nums[i] - nums[j]) <= t ,同时又满足 abs(i - j) <= k 。

如果存在则返回 true,不存在返回 false。

输入:nums = [1,2,3,1], k = 3, t = 0
输出:true
 

 
  1. class Solution {
  2. public boolean containsNearbyAlmostDuplicate(int[] nums, int k, int t) {
  3. TreeSet<Long> set = new TreeSet<>();
  4. for(int i = 0; i < nums.length; i++){
  5. Long ceil = set.ceiling((long)nums[i]-t);
  6. if(ceil != null && (long)(ceil-nums[i]) <= t){
  7. return true;
  8. }
  9. set.add((long)nums[i]);
  10. if(i >= k){
  11. set.remove((long)nums[i-k]);
  12. }
  13. }
  14. return false;
  15. }
  16. }
 

本题小结:(1)最后会出现大数,要用long,并且要判断null,所以注意Long

                  (2)ceiling返回大于等于那个数的最小数

                  (3)开始的ceiling要减去t

                  (4)remove去除的元素是最左边的那个,for循环去除即可

 

参考来源:【1】leetcode 滑动窗口 

                  【2】CSDN 惊鸿只为卿 滑动窗口详解

                  【3】leetcode 官方题解  存在重复元素 III

posted @ 2023-08-11 16:31  CharyGao  阅读(265)  评论(0编辑  收藏  举报