HOT100-子串

发布于 2024-02-11  292 次阅读


AI 摘要

文章标题:HOT100-子串 正文:这篇文章介绍了三个与子串相关的算法。 第一个算法是和为K的子数组。通过维护一个前缀和的哈希表,可以在O(n)的时间复杂度内找到和为K的子数组的个数。 第二个算法是滑动窗口最大值。利用双端队列手动实现单调队列,可以在O(n)的时间复杂度内找到滑动窗口中的最大值。 第三个算法是最小覆盖子串。通过维护两个哈希表来跟踪所需的字符和窗口中的字符,可以在O(n)的时间复杂度内找到最小覆盖子串。 这三个算法都是高频的子串算法,具有实际应用价值。

560. 和为 K 的子数组

class Solution {
    public int subarraySum(int[] nums, int k) {
        int count = 0, pre = 0;//
        // key:前缀和,value:key 对应的前缀和的个数
        HashMap<Integer, Integer> mp = new HashMap<>();
        // 对于下标为 0 的元素,前缀和为 0,个数为 1
        mp.put(0, 1);
        for(int i = 0;i < nums.length;i++){
            pre += nums[i];//前辍和
            if(mp.containsKey(pre - k)){// 先获得前缀和为 pre - k 的个数,加到计数变量里
                count += mp.get(pre - k);
            }
            mp.put(pre, mp.getOrDefault(pre, 0) + 1);// 然后维护 mp 的定义
        }
        return count;
    }
}

239. 滑动窗口最大值

//利用双端队列手动实现单调队列
/**
 * 用一个单调队列来存储对应的下标,每当窗口滑动的时候,直接取队列的头部指针对应的值放入结果集即可
 * 单调队列类似 (tail -->) 3 --> 2 --> 1 --> 0 (--> head) (右边为头结点,元素存的是下标)
 */
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;
    }
}

76. 最小覆盖子串

class Solution {
    public String minWindow(String s, String t) {
        Map<Character, Integer> need = new HashMap<>();
        Map<Character, Integer> window = new HashMap<>();
        for (char c : t.toCharArray()) {
            need.put(c, need.getOrDefault(c, 0) + 1);//记录字符串t中每个字符出现的次数
        }
        int left = 0, right = 0;//滑动窗口的左右边界
        int valid = 0;//对应需要的值
        int start = 0, len = Integer.MAX_VALUE;
        while (right < s.length()) {//右指针小于s时
            char c = s.charAt(right);//更新有边界
            right++;
            if (need.containsKey(c)) {//如果当前更新的值等于need中需要的字符串
                window.put(c, window.getOrDefault(c, 0) + 1);//更新窗口的值
                if (window.get(c).equals(need.get(c))) {//如果字符需要的值已经对上窗口的值,证明这个字符串已经够了,valid++
                    valid++;
                }
            }
            while (valid == need.size()) {//当窗口中包含了字符串t中的所有字符时,开始缩小窗口的大小,直到窗口中不再包含字符串t中的所有字符。
                if (right - left < len) {
                    start = left;
                    len = right - left;
                }
                char d = s.charAt(left);
                left++;//更新左边界,缩小
                if (need.containsKey(d)) {
                    if (window.get(d).equals(need.get(d))) {
                        valid--;
                    }
                    window.put(d, window.getOrDefault(d, 0) - 1);
                }
            }
        }
        return len == Integer.MAX_VALUE ? "" : s.substring(start, start + len);//返回结果
    }
}

后面两道hard题非常有难度。

首先560利用了前辍和,把contains(pre - k)即符合当前情况的value全部加到count上面 从而实现了计数功能。

239关键就是利用双端队列手动实现单调队列。使得队列中不断维护一个左边是最大的队列,如果不是就弹出。

76的重点就是valid,把每个字母需要的次数单独提出来,当全部valid后,就开始缩小窗口,返回最小覆盖子串,直到valid对不上为止。