LeetCode Hot 100:子串

Leetcode Hot 100

子串

和为 K 的子数组

  • 链接:560. 和为 K 的子数组 - 力扣(LeetCode)

  • 题目:给你一个整数数组 nums 和一个整数 k ,请你统计并返回 该数组中和为 k 的子数组的个数。(子数组是数组中元素的连续非空序列。

  • 解决:

    • 暴力枚举:两层循环遍历

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      public int subarraySum(int[] nums, int k) {
      int res = 0;
      for(int left = 0; left < nums.length; left++) {
      int sum = 0;
      for(int right = left; right < nums.length; right++){//以left为开头,符合的子数组
      sum += nums[right];
      if(sum == k){
      res++;
      }
      }
      }
      return res;
      }
    • 前缀和+哈希表优化:哈希表存储存在的前缀和的个数

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      public int subarraySum(int[] nums, int k) {
      int count = 0, pre = 0;
      HashMap<Integer, Integer> mp = new HashMap<>();//哈希表存储前缀和个数
      mp.put(0, 1);//前缀和为0的有1个
      for(int i = 0; i < nums.length; i++){
      pre += nums[i];//当前前缀和
      if(mp.containsKey(pre - k)){
      count += mp.get(pre - k);
      }
      mp.put(pre, mp.getOrDefault(pre, 0) + 1);
      }
      return count;
      }

滑动窗口最大值

  • 链接:239. 滑动窗口最大值 - 力扣(LeetCode)

  • 题目:整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。返回滑动窗口中的最大值。

  • 解决:

    • 优先队列

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      public int[] maxSlidingWindow(int[] nums, int k) {
      //优先队列
      PriorityQueue<int[]> pq = new PriorityQueue<>(new Comparator<int[]>(){
      public int compare(int[] p1, int[] p2){
      return p1[0] != p2[0] ? p2[0] - p1[0] : p2[1] - p1[1];
      }
      });

      for(int i=0; i<k; i++){//把前k个数放入优先队列,存放值和下标
      pq.offer(new int[]{nums[i], i});
      }

      int[] res = new int[nums.length - k + 1];//存放结果的数组
      res[0] = pq.peek()[0];
      for(int i=k; i<nums.length; i++){
      pq.offer(new int[]{nums[i], i});//新元素放入优先队列
      while(pq.peek()[1] <= i-k){//当堆顶的元素已经不在窗口内,删除
      pq.poll();
      }
      res[i-k+1] = pq.peek()[0];//当前窗口的最大值就算堆顶元素
      }

      return res;
      }
    • 单调队列

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      public int[] maxSlidingWindow(int[] nums, int k) {
      //单调队列:队列中存储递减的区间下标
      Deque<Integer> deque = new LinkedList<>();
      for(int i=0; i<k; i++){//存储第一个大小为k的窗口中递减的下标
      while(!deque.isEmpty() && nums[i] >= nums[deque.peekLast()]){
      deque.pollLast();//删除比当前值小的
      }
      deque.offerLast(i);
      }

      int[] res = new int[nums.length - k + 1];//存放结果的数组
      res[0] = nums[deque.peekFirst()];//当前队列头就是窗口最大值
      for(int i=k; i<nums.length; i++){
      while(!deque.isEmpty() && nums[i] >= nums[deque.peekLast()]){
      deque.pollLast();//删除比当前值小的
      }
      deque.offerLast(i);//到这里和上面加入窗口的一样,下面还需要删除不在窗口中的数

      while(deque.peekFirst() <= i-k){//删除不在滑动窗口中的数
      deque.pollFirst();
      }
      res[i-k+1] = nums[deque.peekFirst()];
      }

      return res;
      }
    • 分块+预处理

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      public int[] maxSlidingWindow(int[] nums, int k) {
      //分块+预处理
      //每个分块大小为k,进行分块
      int[] prefixMax = new int[nums.length];//以i为结尾的后缀的分块的最大值
      int[] suffixMax = new int[nums.length];//以i为开始的前缀的分块的最大值

      for(int i=0; i<nums.length; i++){//后缀最大值从头开始更新
      if(i%k == 0){
      prefixMax[i] = nums[i];
      }else{
      prefixMax[i] = Math.max(prefixMax[i-1], nums[i]);
      }
      }

      for(int i=nums.length-1; i>=0; i--){//前缀最大值从尾开始更新
      if(i%k == 0 || i == nums.length-1){
      suffixMax[i] = nums[i];
      }else{
      suffixMax[i] = Math.max(suffixMax[i+1], nums[i]);
      }
      }

      int[] res = new int[nums.length - k + 1];
      for(int i=0; i<=nums.length-k; i++){
      res[i] = Math.max(suffixMax[i], prefixMax[i+k-1]);
      //以i为开头的前缀的最大值和以i+k-1为结尾的最大值中的最大值即为该窗口最大值
      }

      return res;
      }

最小覆盖子串

  • 链接:76. 最小覆盖子串 - 力扣(LeetCode)

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

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

    • 滑动窗口:先更新该窗口,然后不断收缩当前符合条件的窗口,找到最小的子串。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      class Solution {
      Map<Character, Integer> ori = new HashMap<>();
      Map<Character, Integer> cnt = new HashMap<>();

      public String minWindow(String s, String t) {
      for(int i=0; i<t.length(); i++){//存储字符串t中各个字符的个数
      char c = t.charAt(i);
      ori.put(c, ori.getOrDefault(c, 0) + 1);
      }

      int len = Integer.MAX_VALUE;
      int resLeft = -1;
      int resRight = -1;
      for(int left=0,right = 0; right<s.length(); right++){
      if(right < s.length() && ori.containsKey(s.charAt(right))){
      //如果当前字母在t中出现,更新cnt表
      cnt.put(s.charAt(right), cnt.getOrDefault(s.charAt(right), 0) + 1);
      }
      //判断当前cnt表是否符合要求,同时收缩窗口
      while(check() && left <= right){
      if(right - left + 1 < len){//更新当前最小的符合子串
      len = right - left + 1;
      resLeft = left;
      resRight = right + 1;
      }
      if(ori.containsKey(s.charAt(left))){//收缩窗口,更新cnt表
      cnt.put(s.charAt(left), cnt.getOrDefault(s.charAt(left), 0) - 1);
      }
      left++;//收缩窗口则left++
      }
      }
      return resLeft == -1 ? "" : s.substring(resLeft, resRight);
      }

      public boolean check(){//判断cnt中字符个数是否满足ori的要求
      //遍历Map
      Iterator iter = ori.entrySet().iterator();
      while(iter.hasNext()){
      Map.Entry entry = (Map.Entry) iter.next();
      Character key = (Character) entry.getKey();
      Integer val = (Integer) entry.getValue();
      if(cnt.getOrDefault(key, 0) < val){
      //只要cnt中该字符大于等于ori中字符,都满足要求
      return false;
      }
      }
      return true;
      }
      }
    • 滑动窗口(优化):通过count数组存储不同,differ维护不同字符的个数。和【438. 找到字符串中所有字母异位词】这道题的优化思路一样。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      class Solution {
      public String minWindow(String s, String t) {
      //优化:通过count数组存储不同,differ维护不同字符的个数
      if(t.length() > s.length()){
      return "";
      }
      int[] count = new int[128];//存储字符个数
      int differ = 0;//s和t中不同字符的个数
      for(int i=0; i<t.length(); i++){
      char ch = t.charAt(i);
      if(count[ch] == 0){
      differ++;//不同的字符的个数
      }
      count[ch]++;
      }

      int resLeft = -1;
      int resRight = s.length();
      for(int left=0, right = 0; right<s.length(); right++){
      count[s.charAt(right)]--;
      //s中存在该字符,则缺少的个数减一;如果s中存在的字符t中不存在,这里会变成负数,不用考虑
      if(count[s.charAt(right)] == 0){
      differ--;
      }
      while(differ == 0 && left <= right){//符合要求
      if(right - left < resRight - resLeft){
      //如果当前符合的子串更小,更新为当前子串
      resRight = right;
      resLeft = left;
      }
      //收缩窗口
      char ch = s.charAt(left);
      if(count[ch] == 0){
      differ++;
      }
      count[ch]++;//收缩左端点left,则s中缺少该字符的个数加一
      left++;//收缩左端点
      }
      }

      return resLeft == -1 ? "" : s.substring(resLeft, resRight+1);
      }
      }

LeetCode Hot 100:子串
http://surourou8.github.io/2025/04/12/LeetCode-Hot-100:子串/
作者
Su Rourou
发布于
2025年4月12日
许可协议