力扣100及题解 滑动窗口&子串

 目录

1.力扣100题解及笔记 哈希-CSDN博客

2.力扣100题解及笔记 堆-CSDN博客

3.力扣100题解及笔记 栈-CSDN博客

4.力扣100题解及笔记 双指针-CSDN博客

5.力扣100题解及笔记 链表-CSDN博客

6.力扣100题解及笔记 二叉树-CSDN博客

7.力扣100题解及笔记 二分查找-CSDN博客

8.力扣100及题解 滑动窗口&子串-CSDN博客

9.力扣100题解及笔记 回溯-CSDN博客

10.力扣100题解及笔记 dp&多维dp-CSDN博客

11.力扣100题解及笔记 贪心-CSDN博客

12.力扣100题解及笔记 数组-CSDN博客

13.力扣100题解及笔记 技巧-CSDN博客

14.力扣100题解及笔记 矩阵-CSDN博客

15.力扣100题解及笔记 图论-CSDN博客

理论基础

滑动窗口

(可参见双指针篇)

双指针同时从一端出发,一个指针作为窗口的左边界,另一个指针作为右边界,将问题分解为两步:

  • 扩大窗口:通过增加窗口右边界,尝试找到一个满足条件的子串。
  • 缩小窗口:当窗口不满足条件时,通过移动左边界缩小窗口,直到条件再次满足

适用于求子数组或子串的最大值、最小值或满足某些条件的长度等。

子串题型

子串:连续的字符片段。例如,字符串 "abcdef" 的子串包括 "abc" 和 "def",但不包括 "ace"

最长无重复字符的子串

在给定字符串中,寻找没有重复字符的最长子串。

  • 滑动窗口,同时使用一个哈希集合来跟踪当前窗口中的字符。
    1. 初始化两个指针 L 和 R,分别表示窗口的左端和右端。
    2. 移动 R 扩展窗口,遇到重复字符时,移动 L 收缩窗口。
    3. 记录最大长度

包含所有字符的最小子串

给定两个字符串 s 和 t,找到 s 中包含 t 所有字符的最短子串。

  • 使用滑动窗口和一个字符计数的哈希表来跟踪是否满足条件。
    1. 初始化两个指针 L 和 R,维护一个哈希表来记录字符的频率。
    2. 当窗口包含 t 的所有字符时,收缩窗口以寻找最短子串。
    3. 记录最小长度。

固定长度的滑动窗口最大值

给定一个数组,找到所有长度为 k 的滑动窗口的最大值。

  • 使用双端队列来维护当前窗口中的最大元素。
    1. 使用队列存储当前窗口中可能成为最大值的元素。
    2. 每次移动窗口时,移除过期的元素,并更新当前最大值。

3.无重复最长子串

3. 无重复字符的最长子串

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

 

示例 1:

输入: s = "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:

输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:

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

提示:

0 <= s.length <= 5 * 104
s 由英文字母、数字、符号和空格组成

class Solution {
    public int lengthOfLongestSubstring(String s) {
        if (s == null || s.length() == 0)  return 0;
        Map<Character,Integer> m=new HashMap<>();//键是字母,值是对应索引
        int L=-1,l=s.length(),res=0;//length是方法
        for(int R=0;R<l;R++){
            if(m.containsKey(s.charAt(R))) L=Math.max(L,m.get(s.charAt(R)));
            //m.get(s.charAt(R))字符的上次出现位置
            //L跳到上一次字符的位置
            m.put(s.charAt(R),R);//R遍历,哈希表记录
            res=Math.max(res,R-L);
        }
        return res;
    }
}

438.所有异位词

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

给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。

异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。

 

示例 1:

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

输入: s = "abab", p = "ab"
输出: [0,1,2]
解释:
起始索引等于 0 的子串是 "ab", 它是 "ab" 的异位词。
起始索引等于 1 的子串是 "ba", 它是 "ab" 的异位词。
起始索引等于 2 的子串是 "ab", 它是 "ab" 的异位词。
 

提示:

1 <= s.length, p.length <= 3 * 104
s 和 p 仅包含小写字母

class Solution {
    public List<Integer> findAnagrams(String s, String p) {
        List<Integer> res = new ArrayList<>();
        int sl = s.length(), pl = p.length();
        if (pl>sl) return res;
        int[] c1 = new int[26], c2 = new int[26];// c1: s窗口内字符的频次,c2: p 中字符的频次
        for (int i = 0; i < pl; i++) c2[p.charAt(i) - 'a']++;//统计 p 中每个字符出现的频次
        for (int l = 0, r = 0; r < sl; r++) {
            c1[s.charAt(r) - 'a']++; // 右边加入窗口
            if (r - l + 1 > pl) c1[s.charAt(l++) - 'a']--; // 窗口的大小大于 pl,l右移缩小窗口
            if (check(c1, c2)) res.add(l); //左边界l为异位词开始索引
        }
        return res;
    }
    boolean check(int[] c1, int[] c2) {//字符频次一致?
        for (int i = 0; i < 26; i++) {
            if (c1[i] != c2[i]) return false;
        }
        return true;
    }
}

560.和为K子数组

560. 和为 K 的子数组

给你一个整数数组 nums 和一个整数 k ,请你统计并返回 该数组中和为 k 的子数组的个数 。

子数组是数组中元素的连续非空序列。

 

示例 1:

输入:nums = [1,1,1], k = 2
输出:2
示例 2:

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

提示:

1 <= nums.length <= 2 * 104
-1000 <= nums[i] <= 1000
-107 <= k <= 107

前缀和ps是数组中从第一个元素开始,到当前元素为止所有元素的和

要找的子数组的和是 k,要找的子数组的前缀和应该是 sum - k

class Solution {
    public int subarraySum(int[] nums, int k) {
        Map<Integer, Integer> ps = new HashMap<>();
        ps.put(0, 1); // 前缀和为0的次数初始化为1
        int sum = 0,res=0;  
        for (int n: nums) {
            sum += n;//总计和
            if (ps.containsKey(sum - k)) res += ps.get(sum - k);//检查 ps 中是否存在 sum - k,存在加入res
            ps.put(sum, ps.getOrDefault(sum, 0) + 1);//更新 ps
        }
    return res;
    }
}

239.窗口最大值h

239. 滑动窗口最大值

给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。

返回 滑动窗口中的最大值 。


提示:

1 <= nums.length <= 105
-104 <= nums[i] <= 104
1 <= k <= nums.length

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        int l=nums.length;
        if(l== 0 || k == 0) return new int[0];
        Deque<Integer> d = new LinkedList<>(); // 递减双端队列
        int[] res = new int[l - k + 1]; // l - k + 1 是窗口在数组中滑动时的次数
        for(int R = 0, L = 1 - k; R < l; L++, R++) {
            if(L > 0 && d.peekFirst()==nums[L - 1])  d.removeFirst();//删除滑出窗口的元素(检查队最大值是否超出窗口范围)
            while(!d.isEmpty() && d.peekLast() < nums[R]) d.removeLast();//移除所有比 nums[R] 小的元素(未来不可能最大值),保持d递减
            d.addLast(nums[R]);//将当前元素加入队列
            if(L >= 0) res[L] = d.peekFirst(); //存当前窗口的最大值
        }       
        return res;
    }
}

76.最小覆盖子串h

76. 最小覆盖子串

给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。

 

注意:

对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
如果 s 中存在这样的子串,我们保证它是唯一的答案。
 

示例 1:

输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"
解释:最小覆盖子串 "BANC" 包含来自字符串 t 的 'A'、'B' 和 'C'。
示例 2:

输入:s = "a", t = "a"
输出:"a"
解释:整个字符串 s 是最小覆盖子串。
示例 3:

输入: s = "a", t = "aa"
输出: ""
解释: t 中两个字符 'a' 均应包含在 s 的子串中,
因此没有符合条件的子字符串,返回空字符串。
 

提示:

m == s.length
n == t.length
1 <= m, n <= 105
s 和 t 由英文字母组成
 

进阶:你能设计一个在 o(m+n) 时间内解决此问题的算法吗?
class Solution {
    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);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值