滑动窗口 -(LeetCode 209. 长度最小的子数组、904. 水果成篮)

跟着代码随想录完成了 leetcode 209.长度最小的子数组,学习到了滑动窗口的思想。于是做了904题。

题目意思是,遍历数组,找到包含两种元素的最长子串(字串就包含“连续”之意),返回其长度。因此可以利用滑动数组的思想。

想法过程:

滑动数组的基本实现为:left在左缩小窗口,right(i)遍历数组增大窗口。首先left固定不动,通过right扩大窗口以达到“刚好”满足要求(比如当前字串的和大于target,见209题);然后right固定不动,通过left缩小窗口,达到“极限”,当窗口达到“刚好”不满足要求的边缘条件,就用right扩大窗口。

怎么在滑动左边之后,判断当前水果是两个种类还是一个?可以通过现有的变量left、right、sum(当前遍历过的元素之和)吗?不行。

增加什么变量?变量记录:两个种类v1 v2、及其数量c1 c2。

情况总结:

(1)最开始,c1=c2=0,第一个碰到的就算作第一个种类。v1 = fruits[0]; c1++;
(2)第二步,此时只有一个种类。
fruits[i] 如果与当前水果种类相同,c1++; 如果不同,增加种类v2 = fruits[i]; c2++;
(3)第三步,如果当前水果种类属于v1,v2,对应进行c1++,c2++
……
(4)一直到某个i,fruits[i]不属于当前v1,v2。
此时的窗口长度为 c1+c2。 res = Math.max(res,c1+c2);

=>开始滑动窗口左侧 (left),目的:直到窗口内部只剩下一个种类为止。
把此时的新种类 fruits[i] 增加到 v1或者 v2(主要取决于哪个为零),然后回到第二步,直到跳出for循环。

唉,考虑情况的时候不充分,都是在debug过程中又考虑到了新情况。

错误的点:

(1)除去最开始,c1和才c2可能同时为零吗?=>不可能,窗口左侧极端情况为缩减到只剩一个 ,然后就需要接着往下判断。
[1,1,2],3,4,5
=>1,1,[2],3,4,5
(2)跳出for循环后
res是返回值,最初设定为-1。
目标字串可能在数组首部、尾部、中部(情况等价于① 窗口right小于fruit.length ② 窗口right等于fruit.length-1)。

代码:

(非常的冗余,但运行成功。这里只做记录,不推荐。)

class _Solution904_2 {
    public int totalFruit(int[] fruits) {
        int res = -1;
        int sum = 0;
        //滑动窗口:要遍历整个数组,求出最大的
        //怎么在滑动左边之后,判断当前数组是两个种类还是一个?
        //可以通过现有的变量吗?left right?不行:是无规则的
        //增加什么变量?两个种类、及其数量
        int c1=0,c2=0; //记录两个种类的数量
        int v1= -1,v2=- 1; //记录两个种类
        int left=0;
        for(int i = 0; i < fruits.length; i++) {
            //最开始
            if(c1 == 0 && c2 == 0) {
                v1 = fruits[i];
                c1++;
                continue;
            }

            //除去最开始,c1和c2可能同时为零吗?
            //=>不可能,假设窗口左侧缩减到只剩一个 ,然后就需要接着扩大窗口。
            if(c2 == 0 || c1 == 0) { //目前只有一个种类
                //此时只有一个种类,但不知道哪一个为空。
                //为什么不知道哪一个为空,
                //因为 下文滑动窗口左侧缩减直到窗口只剩下一个种类的时候,不知道是v1没了,还是v2没了
                if(c2 == 0){
                    if(fruits[i] == v1) {
                        c1++;
                    } else {
                        v2 = fruits[i];
                        c2++;
                    }
                } else if(c1 == 0) {//c2不为空
                    if (fruits[i] == v2) {
                        c2++;
                    } else {
                        v1 = fruits[i];
                        c1++;
                    }
                }
            } else {//目前已经有两个种类
                if(fruits[i] == v1) {
                    c1++;
                } else if(fruits[i] == v2) {
                    c2++;
                } else { //当前的fruits[i]不属于v1 v2,需要重新建立窗口
                    res = Math.max(res,c1+c2);
                    //while去掉一个种类
                    while(c1 != 0 && c2 != 0){
                        if(fruits[left] == v1) {
                            c1--;
                            left++;
                        } else if(fruits[left] == v2) {
                            c2--;
                            left++;
                        }
                    }
                    if(c1 == 0) {//去掉一个种类之后,要加上当前 种类
                        v1= fruits[i];
                        c1++;
                    }
                    if(c2 == 0) {
                        v2= fruits[i];
                        c2++;
                    }
                }
            }
        }

        if(res == -1) {//数组中只有两个种类, 比如fruits = [1,2,1]
            res=c1+c2;
        } else { //目标子串有可能是数组末尾 [0,1,2,2]
            res = Math.max(res,c1+c2);
        }
        return res;
    }
}//end class

改进

(1)上述过程的 v1 v2 c1 c2 其实可以用 HashMap 替代
(2)请品味代码 res = Math.max(res, right-left+1); 的位置。

class Solution {
    public int totalFruit(int[] fruits) {
        Map<Integer, Integer> cnt = new HashMap<Integer, Integer>();
        int left = 0, res = 0;
        for(int right = 0; right < fruits.length; right++) {
            cnt.put(fruits[right], cnt.getOrDefault(fruits[right], 0) + 1);
            while(cnt.size() > 2) {
                cnt.put(fruits[left], cnt.get(fruits[left])-1);
                if(cnt.get(fruits[left]) == 0) {
                    cnt.remove(fruits[left]);
                }
                left++;
            }
            res = Math.max(res, right-left+1);
        }
        return res;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值