给自己复盘的随想录笔记-长度最小的子数组

滑动窗口

开始介绍数组操作中另一个重要的方法:滑动窗口

所谓滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果

可以发现滑动窗口也可以理解为双指针法的一种!只不过这种解法更像是一个窗口的移动,所以叫做滑动窗口更适合一些。

在本题中实现滑动窗口,主要确定如下三点:

  • 窗口内是什么?
  • 如何移动窗口的起始位置?
  • 如何移动窗口的结束位置?

窗口就是 满足其和 ≥ s 的长度最小的 连续 子数组。

窗口的起始位置如何移动:如果当前窗口的值大于等于s了,窗口就要向前移动了(也就是该缩小了)。

窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,也就是for循环里的索引。

解题的关键在于 窗口的起始位置如何移动,如图所示:

leetcode_209

可以发现滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)暴力解法降为O(n)。

class Solution {
    public int minSubArrayLen(int target, int[] nums) {
   int left=0;
   int right=0;
   int sum=0;
   int result=Integer.MAX_VALUE;

   for(right=0;right<nums.length;right++){
    sum+=nums[right];
    while(sum>=target){
        result=Math.min(result,right-left+1);
        sum-=nums[left];
        left++;
    }
   }
     return result==Integer.MAX_VALUE?0:result;

    }
}

相关题目

水果成篮

这个题目其实和上面的求最小长度的数组的一个意思,套了一个实际情景比较难理解。

class Solution {
    public int totalFruit(int[] fs) {
//ans 为存放水果的数目,类比上一道题目数组的长度   
     
int n = fs.length, ans = 0;

//cnts 代码定义一个数组,数组的序号为水果的种类,数组的值为对应水果种类出现的次数,这个地方写的非常好,我在写这道题目的时候就卡在如何将第一次出现的水果设为水果种类+1,通过构造一个新的数组存放这样的数据
        int[] cnts = new int[n + 10];

//窗口为摘取的水果数量,i对应窗口的最终位置,j对应窗口的初始位置
//tot为篮子里面的水果种类数目,这是本题目的题目条件

        for (int i = 0, j = 0, tot = 0; i < n; i++) {
//i种类的水果第一次出现,篮子种类水果种类+1
            if (++cnts[fs[i]] == 1) tot++;
//水果种类超过题目要求,移动的时候判断这个位置的水果是否之前算进去水果种类里面,现在移动了就需要减去水果种类,然后移动窗口的初始位置
            while (tot > 2) {
                if (--cnts[fs[j++]] == 0) tot--;
            }

//这个地方取结果的代码和前面放的地方不一样,这个取决于题目条件,前面窗口移动时的条件是那个数组长度是要求数组和大于等于目标值,就是我们需要的,所以直接放到里面取结果。这个题目正好相反,窗口移动的条件不是我们需要的,所以在每次循环一次之后去取结果,而不是直接放进去取结果
            ans = Math.max(ans, i - j + 1);
        }
        return ans;
    }
}

最小覆盖子串

76. 最小覆盖子串 - 力扣(LeetCode)

这个题目其实和前面的水果成篮的思路很像,也是新建一个数组,数组的序号是对应字符,数组的值是对应字符出现的次数,窗口移动的条件是s字符串能够完全覆盖r字符串的每一个字符,这个正好是我们所需要的,所以直接在这个移动窗口然后取结果也就是更新s答案字符串的左右边界

具体实现思路

什么是「涵盖」
看示例 1,s 的子串 BANC 中每个字母的出现次数,都大于等于 t=ABC 中每个字母的出现次数,这就叫涵盖。

滑动窗口怎么滑
原理和 209 题一样,按照视频中的做法,我们枚举 s 子串的右端点 right(子串最后一个字母的下标),如果子串涵盖 t,就不断移动左端点 left 直到不涵盖为止。在移动过程中更新最短子串的左右端点。

具体来说:

class Solution {
    public String minWindow(String S, String t) {
        char[] s = S.toCharArray();
        int m = s.length;
        int ansLeft = -1;
        int ansRight = m;
        int left = 0;
        int[] cntS = new int[128]; // s 子串字母的出现次数
        int[] cntT = new int[128]; // t 中字母的出现次数
        for (char c : t.toCharArray()) {
            cntT[c]++;
        }
        for (int right = 0; right < m; right++) { // 移动子串右端点
            cntS[s[right]]++; // 右端点字母移入子串
            while (isCovered(cntS, cntT)) { // 涵盖
                if (right - left < ansRight - ansLeft) { // 找到更短的子串
                    ansLeft = left; // 记录此时的左右端点
                    ansRight = right;
                }
                cntS[s[left++]]--; // 左端点字母移出子串
            }
        }
        return ansLeft < 0 ? "" : S.substring(ansLeft, ansRight + 1);
    }

    private boolean isCovered(int[] cntS, int[] cntT) {
        for (int i = 'A'; i <= 'Z'; i++) {
            if (cntS[i] < cntT[i]) {
                return false;
            }
        }
        for (int i = 'a'; i <= 'z'; i++) {
            if (cntS[i] < cntT[i]) {
                return false;
            }
        }
        return true;
    }
}

优化代码

class Solution {
    public String minWindow(String S, String t) {
        char[] s = S.toCharArray();
        int m = s.length;
        int ansLeft = -1;
        int ansRight = m;
        int left = 0;
        int less = 0;
        int[] cntS = new int[128]; // s 子串字母的出现次数
        int[] cntT = new int[128]; // t 中字母的出现次数
        for (char c : t.toCharArray()) {
            if (cntT[c]++ == 0) {
                less++; // 有 less 种字母的出现次数 < t 中的字母出现次数
            }
        }
        for (int right = 0; right < m; right++) { // 移动子串右端点
            char c = s[right]; // 右端点字母(移入子串)
            if (++cntS[c] == cntT[c]) {
                less--; // c 的出现次数从 < 变成 >=
            }
            while (less == 0) { // 涵盖:所有字母的出现次数都是 >=
                if (right - left < ansRight - ansLeft) { // 找到更短的子串
                    ansLeft = left; // 记录此时的左右端点
                    ansRight = right;
                }
                char x = s[left++]; // 左端点字母(移出子串)
                if (cntS[x]-- == cntT[x]) {
                    less++; // x 的出现次数从 >= 变成 <
                }
            }
        }
        return ansLeft < 0 ? "" : S.substring(ansLeft, ansRight + 1);
    }
}


  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值