LeetCode(Java)经典题之滑动窗口法(76、209、904)

滑动窗口法一般用来处理子数组长度的问题,也需要用到两个指针,通过两个指针的移动,来确定子数组的范围。由于两个指针在不停的移动,子数组的范围也在不停变化,类似于滑动的窗口,所以叫做滑动窗口法。废话少说,看题来理解。

第209题:

给定一个含有n个正整数的数组和一个正整数 target。
找出该数组中满足其和≥ target的长度最小的连续子数组[numsl, numsl+1, ..., numsr-1, numsr],并返回其长度。如果不存在符合条件的子数组,返回 0 。

假定我们的target = 7, nums = [2,3,1,2,4,3]

首先定义左指针(left)和右指针(right)都为0,此时它们都指向nums[0]=2,明显小于target=7,不满足题意,此时右指针后移一位,现在子数组为:[2,3],它们的和为5,不满足题意,right继续右移,直到子数组为[2,3,1,2]时,子数组的和为8,满足题意。

但是我们需要找到长度最小的子数组,所以此时让left右移一位,看还是否满足条件,left右移后子数组为[3,1,2],小于target。right继续右移,找到[3,1,2,4],满足题意,left继续右移变为[1,2,4],仍旧满足题意,可是不能输出结果,万一后面有更短的呢?

left继续后移,子数组为[2,4]不满足题意,那么right后移,子数组为[2,4,3]满足,此时left后移,子数组为[4,3],终于,我们找到了满足题意的最短子数组[4,3]。

在这整个过程中,子数组在不断地变化,所以我们叫做滑动窗口法,代码实现如下:

public class question209 {
    public int minSubArrayLen(int s, int[] nums) {
        int left = 0;//定义左指针
        int right;//定义右指针
        int sum = 0;//定义子数组的和
        int result = Integer.MAX_VALUE;//给最终结果赋值
        for (right = 0;right < nums.length;right++) {
            sum = sum + nums[right];
            while (sum >= s) {
                result = Math.min(result,right - left + 1);
                sum = sum - nums[left];
                left++;
            }
        }
        return result == Integer.MAX_VALUE ? 0 : result;
    }
}

第904题:

你正在探访一家农场,农场从左到右种植了一排果树。
这些树用一个整数数组fruits表示,其中fruits[i]是第i棵树上的水果种类 。
你想要尽可能多地收集水果。然而,农场的主人设定了一些严格的规矩,你必须按照要求采摘水果:
你只有两个篮子,并且每个篮子只能装单一类型的水果。
每个篮子能够装的水果总量没有限制。
你可以选择任意一棵树开始采摘,你必须从每棵树(包括开始采摘的树)上恰好摘一个水果 。
采摘的水果应当符合篮子中的水果类型。每采摘一次,你将会向右移动到下一棵树,并继续采摘。
一旦你走到某棵树前,但水果不符合篮子的水果类型,那么就必须停止采摘。
给你一个整数数组 fruits,返回你可以收集的水果的最大数目。

警告!!!!不要被题目吓到!!认真,细致,转换为编程语言。

说白了,题目就是让找只包含两种元素的最长子数组!!!

这么想的话,比上一题多出一个步骤,就是需要暂时存储我们找到数字的数目,为此,我们创建一个新数组a。

拿fruits = [0,1,2,2]来举例:

首先取了一个0,那么a[0]的位置就加1,这个时候我们已经取了一个种类的水果,所以水果的种类count要加1;

接着取了一个1,a[1]的位置就加1,水果的种类count同样加1;

然后取2,a[2]的位置加1,种类count加1,可此时count=3,超出了题目要求,所以就舍弃最先取下来的0,这个时候a[0]的位置就要减1,count也对应减1,

接着取最后一个2,a[2]的位置再加1,变成2,此时count还是2,不变,最终找到了结果[1,2,2]

以下是代码实现部分:

public class question904 {
    public int totalFruit(int[] fruits) {
        int left = 0;
        int right;
        int total = 2;//收集水果的个数(最少也能拿两个)
        int[] frequenceFruit = new int[fruits.length];//这个数组盛放:每一种水果拿了几次
        int count = 0;//统计拿了几种水果
        //优先考虑特殊情况:水果的种类小于等于两种
        if (fruits.length <= 2) {
            return fruits.length;
        }
        for (right = 0;right < fruits.length;right++) {
            frequenceFruit[fruits[right]]++;//对应的一种水果的数目+1
            if (frequenceFruit[fruits[right]] == 1) {
                count++;//水果的种类+1
            }
            while (count > 2) {//篮子中水果的种类超过2
                frequenceFruit[fruits[left]]--;
                if (frequenceFruit[fruits[left]] == 0) {
                    count--;
                }
                left++;
            }
            total = Math.max(total,right - left + 1);//取2和子数组长度中最大的
        }
        return total;
    }
}

最后一题,第76题:

给你一个字符串 s 、一个字符串 t 。
返回s中涵盖t所有字符的最小子串。
如果s中不存在涵盖t所有字符的子串,则返回空字符串 "" 。
注意:
对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
如果 s 中存在这样的子串,我们保证它是唯一的答案

这题看上去不复杂,答案的思路我也能看的懂并且理解。可是代码实现真的好复杂,看着看着就晕了,我先把代码贴上来,以后慢慢参透:

public class question76 {
    public String minWindow(String s, String t) {
        if (s == null || s.length() == 0 || t == null || t.length() == 0){
            return "";
        }
        int[] need = new int[128];
        //记录需要的字符的个数
        for (int i = 0; i < t.length(); i++) {
            need[t.charAt(i)]++;
        }
        //left是当前左边界,right是当前右边界,
        //size记录窗口大小,count是需求的字符个数,start是最小覆盖串开始的index
        int left = 0, right = 0, size = Integer.MAX_VALUE, count = t.length(), start = 0;
        //遍历所有字符
        while (right < s.length()) {
            char c = s.charAt(right);
            if (need[c] > 0) {//需要字符c
                count--;
            }
            need[c]--;//把右边的字符加入窗口
            if (count == 0) {//窗口中已经包含所有字符
                while (left < right && need[s.charAt(left)] < 0) {
                    need[s.charAt(left)]++;//释放右边移动出窗口的字符
                    left++;//指针右移
                }
                if (right - left + 1 < size) {//不能右移时候挑战最小窗口大小,更新最小窗口开始的start
                    size = right - left + 1;
                    start = left;//记录下最小值时候的开始位置,最后返回覆盖串时候会用到
                }
                //l向右移动后窗口肯定不能满足了 重新开始循环
                need[s.charAt(left)]++;
                left++;
                count++;
            }
            right++;
        }
        return size == Integer.MAX_VALUE ? "" : s.substring(start, start + size);
    }
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值