day1_slidingWindow

发布于:2024-05-10 ⋅ 阅读:(25) ⋅ 点赞:(0)

一、滑动窗口模板

// 注意:java 代码由 chatGPT🤖 根据我的 cpp 代码翻译,旨在帮助不同背景的读者理解算法逻辑。
// 本代码不保证正确性,仅供参考。如有疑惑,可以参照我写的 cpp 代码对比查看。

import java.util.HashMap;
import java.util.Map;

public class Main {
    /* 滑动窗口算法框架 */
    public static void slidingWindow(String s) {
        // 用合适的数据结构记录窗口中的数据,根据具体场景变通
        // 比如说,我想记录窗口中元素出现的次数,就用 map
        // 我想记录窗口中的元素和,就用 int
        Map<Character, Integer> window = new HashMap<>();
        
        int left = 0, right = 0;
        while (right < s.length()) {
            // c 是将移入窗口的字符
            char c = s.charAt(right);
            window.put(c, window.getOrDefault(c, 0) + 1);
            // 增大窗口
            right++;
            // 进行窗口内数据的一系列更新
            // ...

            /*** debug 输出的位置 ***/
            // 注意在最终的解法代码中不要 print
            // 因为 IO 操作很耗时,可能导致超时
            System.out.printf("window: [%d, %d)\n", left, right);
            /********************/
            
            // 判断左侧窗口是否要收缩
            while (left < right && window needs shrink) {
                // d 是将移出窗口的字符
                char d = s.charAt(left);
                window.put(d, window.get(d) - 1);
                // 缩小窗口
                left++;
                // 进行窗口内数据的一系列更新
                // ...
            }
        }
    }

    public static void main(String[] args) {
        slidingWindow("your string here");
    }
}

在这里插入图片描述

背完模板后,还需要考虑什么?

1、什么时候应该移动 right 扩大窗口?窗口加入字符时,应该更新哪些数据?

2、什么时候窗口应该暂停扩大(满足了条件),开始移动 left 缩小窗口?从窗口移出字符时,应该更新哪些数据?

3、我们要的结果应该在扩大窗口时还是缩小窗口时进行更新?

如果一个字符进入窗口,应该增加 window 计数器;如果一个字符将移出窗口的时候,应该减少 window 计数器;当 valid 满足 need 时应该收缩窗口;应该在收缩窗口的时候更新最终结果。

1.最小覆盖子串
  1. s是长字符串,t是短字符串
  2. need.size() 是 t中 不同字符的种类数,同样的valid 每加1表示的是一个字符被满足,而不是某个字符的数量匹配上了一次
/**
 * 求字符串 s 中包含字符串 t 所有字符的最小子串
 * @param s 源字符串
 * @param t 给定字符串
 * @return 满足条件的最小子串
 */
public String minWindow(String s, String t) {
    // 用于记录需要的字符和窗口中的字符及其出现的次数
    Map<Character, Integer> need = new HashMap<>();
    Map<Character, Integer> window = new HashMap<>();
    // 统计 t 中各字符出现次数
    for (char c : t.toCharArray()) 
        need.put(c, need.getOrDefault(c, 0) + 1);

    int left = 0, right = 0;
    int valid = 0; // 窗口中满足需要的字符个数
    // 记录最小覆盖子串的起始索引及长度
    int start = 0, len = Integer.MAX_VALUE;
    while (right < s.length()) {
        // c 是将移入窗口的字符
        char c = s.charAt(right);
        // 扩大窗口
        right++;
        // 进行窗口内数据的一系列更新
        if (need.containsKey(c)) {
            window.put(c, window.getOrDefault(c, 0) + 1);
            if (window.get(c).equals(need.get(c)))
                valid++; // 只有当 window[c] 和 need[c] 对应的出现次数一致时,才能满足条件,valid 才能 +1
        }

        // 判断左侧窗口是否要收缩
        while (valid == need.size()) {
            // 更新最小覆盖子串
            if (right - left < len) {
                start = left;
                len = right - left;
            }
            // d 是将移出窗口的字符
            char d = s.charAt(left);
            // 缩小窗口
            left++;
            // 进行窗口内数据的一系列更新
            if (need.containsKey(d)) {
                if (window.get(d).equals(need.get(d)))
                    valid--; // 只有当 window[d] 内的出现次数和 need[d] 相等时,才能 -1
                window.put(d, window.get(d) - 1);
            }
        }
    }

    // 返回最小覆盖子串
    return len == Integer.MAX_VALUE ?
        "" : s.substring(start, start + len);
}

在这里插入图片描述

在Java中,对于对象类型(如Integer、String等),我们通常使用equals()方法来判断它们的值是否相等,而不是使用等号==。

原因是,等号 == 在比较对象类型时,比较的是对象的引用(内存地址),而不是对象的实际值。也就是说,使用 == 来比较两个对象,只有当它们引用同一个对象时,才会返回true。

然而,equals()方法被设计用来比较对象的值是否相等。对于String类型,equals()方法已经被重写,用于比较两个字符串的内容是否相等。对于Integer类型,equals()方法也被重写,用于比较两个整数值是否相等。

因此,如果我们想要比较两个对象的值是否相等,应该使用equals()方法。例如,使用s1.equals(s2)来比较两个字符串s1和s2的内容是否相等。

需要注意的是,有些对象类型(如Integer、Character等)具有自动拆装箱的功能,可以直接使用等号==进行比较。这是因为Java会自动将基本类型转换为对应的包装类型,以便进行比较。但是,对于其他对象类型,我们仍然应该使用equals()方法来确保正确的比较

2.字符串的排列
class Solution {
    public boolean checkInclusion(String s1, String s2) {
        HashMap<Character, Integer> need = new HashMap<>();
        HashMap<Character, Integer> window = new HashMap<>();
        for(int i = 0; i < s1.length(); i++){
            char c = s1.charAt(i);
            need.put(c, need.getOrDefault(c, 0) + 1);
        }

        int left = 0,right = 0;
        int valid = 0;
        while(right < s2.length()){
            char c = s2.charAt(right);
            right++;
            if(need.containsKey(c)){
                window.put(c, window.getOrDefault(c, 0) + 1);
                if(window.get(c).equals(need.get(c)))
                    valid++;
            }

            //判断左窗口是否要收缩
            while(right - left >= s1.length()){
                //在这里判断是否找到了合法的子串
                if(valid == need.size())
                    return true;
                char d = s2.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 false;
    }
}

在这里插入图片描述

3.找出所有的字母异位词438
// 注意:java 代码由 chatGPT🤖 根据我的 cpp 代码翻译,旨在帮助不同背景的读者理解算法逻辑。
// 本代码不保证正确性,仅供参考。如有疑惑,可以参照我写的 cpp 代码对比查看。

public List<Integer> findAnagrams(String s, String t) {
    Map<Character, Integer> need = new HashMap<>();
    Map<Character, Integer> window = new HashMap<>();
    for (int i = 0; i < t.length(); i++) {
        char c = t.charAt(i);
        need.put(c, need.getOrDefault(c, 0) + 1);
    }

    int left = 0, right = 0;
    int valid = 0;
    List<Integer> res = new ArrayList<>(); // 记录结果
    while (right < s.length()) {
        char c = s.charAt(right);
        right++;
        // 进行窗口内数据的一系列更新
        if (need.containsKey(c)) {
            window.put(c, window.getOrDefault(c, 0) + 1);
            if (window.get(c).equals(need.get(c))) {
                valid++;
            }
        }
        // 判断左侧窗口是否要收缩
        while (right - left >= t.length()) {
            // 当窗口符合条件时,把起始索引加入 res
            if (valid == need.size()) {
                res.add(left);
            }
            char d = s.charAt(left);
            left++;
            // 进行窗口内数据的一系列更新
            if (need.containsKey(d)) {
                if (window.get(d).equals(need.get(d))) {
                    valid--;
                }
                window.put(d, window.get(d) - 1);
            }
        }
    }
    return res;
}

4.最长无重复子串
int lengthOfLongestSubstring(String s) {
    Map<Character, Integer> window = new HashMap<>();

    int left = 0, right = 0;
    int res = 0; // 记录结果
    while (right < s.length()) {
        char c = s.charAt(right);
        right++;
        // 进行窗口内数据的一系列更新
        window.put(c, window.getOrDefault(c, 0) + 1);
        // 判断左侧窗口是否要收缩
        while (window.get(c) > 1) {
            char d = s.charAt(left);
            left++;
            // 进行窗口内数据的一系列更新
            window.put(d, window.get(d) - 1);
        }
        // 在这里更新答案
        res = Math.max(res, right - left);
    }
    return res;
}