"Algorithm Series" Sliding Window

Introduction

  In fact, the sliding window is a special kind of double-pointer type problem . The two pointers move in the same direction. We are more concerned about the data contained in the two pointers . At this time, it can be called a sliding window type problem. Many solutions are natural to us. You can think of using a sliding window to solve it, for example, "In an array, the sum of the consecutive elements is equal to the target". You can tell at a glance that you should use two pointers as a sliding window, and then calculate the result of the contained values. It is impossible to use a loop, right?

theoretical basis

  In fact, the theoretical basis of the sliding window is the double pointer, which refers to the solution method of a class of problems, which is solved by moving the two pointers in the same direction on the array. In fact, we don't need to give them a special name, and their solutions are actually very natural. However, the problems solved using sliding windows are usually the optimization of violent solutions . The best way to master this type of problem is to practice, and then think clearly why sliding windows can be used.

Problem solving experience

  • The sliding window can also be regarded as a special double-pointer problem, a type of problem solved by moving the two pointers in the same direction.
  • The biggest difference between sliding windows and double pointers is that sliding windows care more about the values ​​in the window, not just the elements on the two pointers.
  • Problems solved using sliding windows are usually optimizations with brute force solutions.
  • The classic sliding window needs to be understood and mastered.
  • Many times sliding windows are used with hash tables.
  • It takes a lot of practice and thinking about why a sliding window is used.

algorithm topic

3. The longest substring without repeating characters

insert image description here
Topic analysis: Use the sliding window idea to solve the problem, use two pointers to select the substring, and then use the hash table to determine whether there are duplicates.
code show as below:

/**
 * 
 */
class Solution {
    public int lengthOfLongestSubstring(String s) {
        int ans = 0;
        char[] arr = s.toCharArray();
        Map<Character, Integer> map = new HashMap<>();
        for (int j = 0, i = 0; j < arr.length; j++) {
            if (map.containsKey(arr[j])) {
                i = Math.max(map.get(arr[j]), i);
            }
            ans = Math.max(ans, j - i + 1);
            map.put(arr[j], j + 1);//下标 + 1 代表 i 要移动的下个位置
        }
        return ans;
    }
}

30. Concatenate substrings of all words

insert image description here
Topic analysis: remember that the length of words is m, the length of each word in words is n, and the length of s is ls. First, s needs to be divided into groups of words, each of size n (except the beginning and the end). There are n kinds of such division methods, that is, after deleting the first i (i=0≈n-1) letters, the remaining letters are divided, and if there are less than n letters at the end, they are also deleted. For the word arrays obtained by these n kinds of divisions, the sliding window is used to search words similar to anagrams of letters .
code show as below:

/**
 * 滑动窗口
 */
 class Solution {
    public List<Integer> findSubstring(String s, String[] words) {
        List<Integer> res = new ArrayList<Integer>();
        int m = words.length, n = words[0].length(), ls = s.length();
        for (int i = 0; i < n; i++) {
            if (i + m * n > ls) {
                break;
            }
            Map<String, Integer> differ = new HashMap<String, Integer>();
            for (int j = 0; j < m; j++) {
                String word = s.substring(i + j * n, i + (j + 1) * n);
                differ.put(word, differ.getOrDefault(word, 0) + 1);
            }
            for (String word : words) {
                differ.put(word, differ.getOrDefault(word, 0) - 1);
                if (differ.get(word) == 0) {
                    differ.remove(word);
                }
            }
            for (int start = i; start < ls - m * n + 1; start += n) {
                if (start != i) {
                    String word = s.substring(start + (m - 1) * n, start + m * n);
                    differ.put(word, differ.getOrDefault(word, 0) + 1);
                    if (differ.get(word) == 0) {
                        differ.remove(word);
                    }
                    word = s.substring(start - n, start);
                    differ.put(word, differ.getOrDefault(word, 0) - 1);
                    if (differ.get(word) == 0) {
                        differ.remove(word);
                    }
                    word = s.substring(start - n, start);
                }
                if (differ.isEmpty()) {
                    res.add(start);
                }
            }
        }
        return res;
    }
}

76. Minimum Covering Substring

insert image description here
Topic analysis: Use the right pointer to continuously expand to find a feasible solution. After finding a feasible solution, use the left pointer to shrink, optimize the feasible solution, and update the result at the same time. The final solution can be obtained by one traversal.
code show as below:

/**
 * 滑动窗口
 */
class Solution {
    public String minWindow(String s, String t) {
        HashMap<Character, Integer> need = new HashMap<>(); // 用来保存所需要匹配的字符个数
        HashMap<Character, Integer> window = new HashMap<>(); // 用来保存当前窗口内能满足匹配的字符个数
        int left = 0, right = 0; // 左右窗口指针,逻辑上定义为左闭右开的区间,这样的话[0,0)时,区间内就没有元素,方便边界处理
        int valid = 0; // 记录当前window有多少个字符满足need,当全部满足时,开始收缩左指针

        int start = 0, end = 0; // 记录满足条件字符串的起始位置
        int len = Integer.MAX_VALUE; // 记录满足条件的字符串的长度,用于更新最终结果

        // 初始化need
        for(int i = 0; i < t.length(); i++){
            need.put(t.charAt(i), need.getOrDefault(t.charAt(i), 0) + 1); // 将相应的键值对存入need
        }

        // 滑动窗口开始工作
        while(right < s.length()){ // 保证right遍历了整个字符串
            // 先右窗口扩张,找到可行解
            char in_ch = s.charAt(right);
            right++;
            if(need.containsKey(in_ch)){ // 如果遍历到的是我们需要的字符,则将其加入window中,保证need和window保存的是同种字符
                window.put(in_ch, window.getOrDefault(in_ch, 0) + 1);

                if(window.get(in_ch).equals(need.get(in_ch))){ // 第一次相等时更新valid, 注意Integer对象要用equals来进行比较,不能用 == 
                    valid++; // 表示有一个字符已经满足了
                }
                
            }
            
            // 如果valid全部满足,则去收缩左窗口,优化可行解
            while(valid == need.size()){
                // 保存当前解
                if(len > right - left){
                    len = right-left;
                    start = left;
                    end = right;
                }

                // 同时收缩左窗口,优化当前可行解
                char out_ch = s.charAt(left);
                left++;
                if(need.containsKey(out_ch)){
                    if(window.get(out_ch).equals(need.get(out_ch))){ // 因为window内的某字符数量可能多于need中的,所以当相等的时候再--
                        valid--;
                    }
                    window.put(out_ch, window.get(out_ch)-1); // 同时window内该字符数量-1
                }

            }          

        }

        return s.substring(start, end);
    }
}

187. Repeated DNA sequences

insert image description here
Topic analysis: Use a hash table to count the number of occurrences of all substrings with a length of 10 in s, and return all substrings whose occurrences exceed 10.
code show as below:

/**
 * 滑动窗口 + 哈希表 
 */
class Solution {
    public List<String> findRepeatedDnaSequences(String s) {
         Set<String> set = new HashSet();
         List<String> res = new ArrayList();
         int len = s.length();
         if(len <= 10) return res;
         for(int i = 0; i < len - 9; i++){
             String str =s.substring(i, i + 10);
             if(!set.add(str) && !res.contains(str))res.add(str);
         }
         return res;
    }
}

209. Minimum Length Subarray

insert image description here
Topic Analysis: Sliding window classic problem, use an int value to compare the size of each window value.
code show as below:

/**
 * 滑动窗口
 */
 class Solution {
    public int minSubArrayLen(int s, int[] nums) {
        int left = 0;
        int sum = 0;
        int result = Integer.MAX_VALUE;
        for (int right = 0; right < nums.length; right++) {
            sum += nums[right];
            while (sum >= s) {
                result = Math.min(result, right - left + 1);
                sum -= nums[left++];
            }
        }
        return result == Integer.MAX_VALUE ? 0 : result;
    }
}

220. The presence of repeating elements III

insert image description here
Topic analysis: For at most k elements on the left side of each element x in the sequence, if one of the k elements falls in the interval [x- t, x +t], we have found a pair of eligible element. Note that for two adjacent elements, k-1 of the k elements to their respective left are coincident. So we can use the idea of ​​sliding windows to maintain a sliding window of size k. Every time we traverse to element x, the sliding window contains at most k elements in front of element x. We check whether there are elements in the window that fall on Just in the interval [x - t, x + t].
code show as below:

/**
 * 滑动窗口 + 有序集合  
 */
class Solution {
    public boolean containsNearbyAlmostDuplicate(int[] nums, int k, int t) {
        // 滑动窗口结合查找表,此时滑动窗口即为查找表本身(控制查找表的大小即可控制窗口大小)
        TreeSet<Long> set = new TreeSet<>();
        for (int i = 0; i < nums.length; i++) {
            // 边添加边查找
            // 查找表中是否有大于等于 nums[i] - t 且小于等于 nums[i] + t 的值
            Long ceiling = set.ceiling((long) nums[i] - (long) t);
            if (ceiling != null && ceiling <= ((long) nums[i] + (long) t)) {
                return true;
            }
            // 添加后,控制查找表(窗口)大小,移除窗口最左边元素
            set.add((long) nums[i]);
            if (set.size() == k + 1) {
                set.remove((long) nums[i - k]);
            }
        }
        return false;
    }
}

239. Maximum Sliding Window

insert image description here
Topic analysis: Use double-ended queues to manually implement monotonic queues, use a monotonic queue to store the corresponding subscripts, and whenever the window slides, directly take the value corresponding to the head pointer of the queue and put it into the result set. Monotonic queues are similar (tail -->) 3 --> 2 --> 1 --> 0 (–> head) (the right is the head node, and the elements are stored as subscripts).
code show as below:

/**
 * 单调队列
 */
class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        ArrayDeque<Integer> deque = new ArrayDeque<>();
        int n = nums.length;
        int[] res = new int[n - k + 1];
        int idx = 0;
        for (int i = 0; i < n; i++) {
            // 根据题意,i为nums下标,是在[i - k + 1, i] 中选到最大值,只需要保证两点
            // 1.队列头结点需要在[i - k + 1, i]范围内,不符合则要弹出
            while (!deque.isEmpty() && deque.peek() < i - k + 1) {
                deque.poll();
            }

            // 2.既然是单调,就要保证每次放进去的数字要比末尾的都大,否则也弹出
            while (!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) {
                deque.pollLast();
            }

            deque.offer(i);

            // 因为单调,当i增长到符合第一个k范围的时候,每滑动一步都将队列头节点放入结果就行了
            if (i >= k - 1) {
                res[idx++] = nums[deque.peek()];
            }
        }
        return res;
    }
}

back to the homepage

Some feelings about brushing Leetcode 500+ questions

Next

Sorting of "Algorithm Series"

Guess you like

Origin blog.csdn.net/qq_22136439/article/details/126445781
Recommended