904. 水果成篮

用一个 Map 记录当前窗口的情况:  key - 水果种类 value - 这个水果种类在当前滑动窗口里出现的次数

维持一个 left 指针到 right 指针的滑动窗口

每次 right 右移一位,将这个新加入窗口的  fruits[right] 种类放到 map 里,并将该种类计数 +1

如果当前窗口水果种类数,即 map 的 size 大于规定的 2

那么需要左指针右移,维持窗口内种类的数量不大于 2

思考下 [1,0,1,4,]1,4,1,2,3 这种情况
在 [1,0,1,4,] 这个窗口,凭人的判断应该将左指针移到 1 这里,窗口变成 [1,4]
但是怎么用代码来实现这个逻辑呢?
我刚开始只用了 Set ,没有用 Map。想窗口最左边的元素是 1,那 left 移动到第一个不等于 1 的地方就行了,显然这种情况不适用

后来又想从窗口最右边往左找第一个不等于 1 的地方,对于这种情况也是不适用的

后面看了题解,正确的做法应该是

left 每右移一格,map 里这个种类窗口计数 -1

并且每次 -1 后判断,当前 left 的种类窗口计数是否减到了0

减到0,说明窗口没有这个种类了,remove 掉

对于上面的例子,窗口 [1,0,1,4]

leftIndex = 0  Map {1:2, 0:1, 4:1}

leftIndex = 1  Map {1:1, 0:1, 4:1}

leftIndex = 2  Map {1:1, 0:0, 4:1} -->  Map {1:1, 4:1}

窗口里只剩两个种类了,可以退出循环了,最后窗口为 [1,4,]

class Solution {
    public int totalFruit(int[] fruits) {
        int left=0;
        int right=0;
        int maxRes = Integer.MIN_VALUE;
        Map<Integer, Integer> curWinKind2CntMap = new HashMap();
        while (right < fruits.length) {
            // 放入窗口右边的水果,并将计数 + 1
            // 注意 getOrDefault
            curWinKind2CntMap.put(fruits[right], curWinKind2CntMap.getOrDefault(fruits[right], 0) + 1);
            // 当窗口内种类数 > 2
            if (curWinKind2CntMap.size() > 2) {
                // left 左移,fruits[left] 种类数 -1
                curWinKind2CntMap.put(fruits[left], curWinKind2CntMap.get(fruits[left]) - 1);
                // 如果减到0,说明没有了,移除掉
                if (curWinKind2CntMap.get(fruits[left]) == 0) {
                    curWinKind2CntMap.remove(fruits[left]);
                }
                left++;
            }
            // 窗口大小
            int winLen = right - left + 1;
            // 与最大值比较
            if (winLen > maxRes) {
                maxRes = winLen;
            }
            right++;
        }
        return maxRes;
    }
}

 

栈 Stack 是 push 与 pop

队列 Queue 是 offer 与 poll

题解:

https://leetcode.cn/problems/sliding-window-maximum/solution/dong-hua-yan-shi-dan-diao-dui-lie-239hua-hc5u/

我们期望:

   维持一个单调递减队列,队列内的元素都是当前窗口内的元素。队列首的元素是最大值,也是当前窗口的最大值

有两个问题:

(1)怎么来维持单调递减呢?也就是说窗口右移的时候,怎么把右边新加入窗口的元素加入到队列中去?

offer 操作:加入队末,前面有比它它小的,就移除,直到队列为空 或 遇到一个比它大的,才推进去

(2)怎么来保证队列中的元素都是窗口内的呢?也就是窗口右移的时候,怎么把左边新移出窗口的元素从队列中去除?

poll 操作:如果是普通的队列,队首的元素直接移除就好了。但是前面的 offer 操作比较特殊,可能窗口最左的元素在前面 offer 的时候因为小于队末新加元素已经被移除了,所以要将队首元素和窗口最左元素比较,如果相等,再将其移除。

这样 peek() 操作,直接获取队首元素,就是当前窗口的最大值。

注意:

  • 以上操作都只是操作队列首和队列尾,所以用 LinkedList 比较合适,多用 getFirst()  removeFirst() getLast() removeLast() 等操作,不要用下标操作
  • 刚开始左右指针 left right 下标都是 0,要左指针不动,右指针右移 k 步完成窗口初始化
  • 后面就是 while(right<nums.length)
                // 将窗口右边的新元素加入单调递减队列
                offer(nums[right++]);
                // 把窗口左边已经移出窗口的元素,也移出队列
                pollIfEquals(nums[left++]);
                // 单调递减队列,队首元素即为当前窗口的的最大值
                res[i++] = peek();
class Solution {

    LinkedList<Integer> queue;

    public Solution() {
        // 用链表类型的 List 来实现
        queue = new LinkedList();
    }

    public int[] maxSlidingWindow(int[] nums, int k) {
        // 最后结果的长度是 nums.length - k + 1
        int resLen = nums.length - k + 1;
        int[] res = new int[resLen];
        int left = 0;
        int right = 0;
        // 窗口初始化
        for (int i=0;i<k;i++) {
            offer(nums[right++]);
        }
        res[0] = peek();
        // 窗口右移
        int i=1;
        while(right<nums.length) {
            // 将窗口右边的新元素加入单调递减队列
            offer(nums[right++]);
            // 按理说应该把窗口左边已经移出窗口的元素,也移出队列
            // 但是可能把右边新元素推到队列的过程中,就已经把这个左边的元素移出去了,所以要比较如果相等,才移出,否则 doNothing
            pollIfEquals(nums[left++]);
            // 单调递减队列,队首元素即为当前窗口的的最大值
            res[i++] = peek();
        }
        return res;
    }

    // 将 num 推到队列中,使其维持单调递减
    private void offer(int num) {
        if (queue.isEmpty()) {
            queue.add(num);
            return;
        }
        // 前面有比它它小的,就移除,直到队列为空 或 遇到一个比它大的,才推进去
        while (queue.size() > 0 && queue.getLast() < num) {
            queue.removeLast();
        }
        queue.add(num);
    }

    // 如果队首元素是 n ,则弹出来。如果不是,do nothing
    private void pollIfEquals(int n) {
        if (peek() == n) {
            queue.removeFirst();
        }
    }

    // 获取队首,因为是单调递减队列,所以也是最大值
    private Integer peek() {
        return queue.getFirst();
    }

}

 

 

560. 和为 K 的子数组

官方题解:https://leetcode.cn/problems/subarray-sum-equals-k/solution/he-wei-kde-zi-shu-zu-by-leetcode-solution/

假设 left 到 right 下标的子数组和为 k
nums[left...right] = k
preSum[right] - preSum[left] = k
preSum[left] = preSum[right] - k
所以每次到 right 的时候,找到等于 preSum[right] - k 的 preSum[left] 有多少个
用一个 map 来记录,前缀和的 count (见官方题解动画)
key: 前缀和的值 value: 前缀和为这个值的个数
map 要放入一个初始值 {0,1}

 

 

class Solution {
    public int subarraySum(int[] nums, int k) {
        Map<Integer, Integer> leftPreSum2CntMap = new HashMap();
        
        leftPreSum2CntMap.put(0, 1);
        int rightPreSum = 0;
        // 总的满足条件的子数组个数的计数,最后的结果
        int count = 0;
        for (int right=0;right<nums.length;right++) {
            rightPreSum += nums[right];
            // [...i]
            // left 到 right 的子数组和为 k
            // nums[left...right] = k
            // preSum[right] - preSum[left] = k
            // preSum[left] = preSum[right] - k
            // 所以每次到 right 的时候,找到有多少个前多少的个的和等于 preSum[right] - k
            Integer leftPreSumCnt = leftPreSum2CntMap.get(rightPreSum - k);
            if (leftPreSumCnt != null) {
                count += leftPreSumCnt;
            }

            Integer rightPreSumCnt = leftPreSum2CntMap.get(rightPreSum);
            if (rightPreSumCnt == null) {
                rightPreSumCnt = 0;
            }
            leftPreSum2CntMap.put(rightPreSum, ++rightPreSumCnt);
        }
        return count;
    }
}

 

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

滑动窗口的大小固定为 p 的长度

维持一个 int[] count = new int[26] 的数组,用来表示 当前滑动窗口内的字符串 与 p这个字符串 的字母数差异

count[i] 表示第 i 个字母的差异数,例如

  • 出现在当前窗口的字母,每出现一次,count[该字母]++。出现在 p 中的字母,每出现一次,count[该字母]--。
  • count[0]=4 表示 当前滑动窗口的字符串 比 p字符串 多4个字母a
  • count[4]=-1 表示 当前滑动窗口的字符串 比 p字符串 少1个字母e

维持一个整数 differ 表示 当前滑动窗口的字符串 与 p字符串 在多少个字母上有差异。例如:

  • count 中的非零值有 count[0]=4 count[4]=-1,则表示在字母 a、e 上有差异,differ=2

为什么需要 differ?

  • 判断 当前滑动窗口字符串字符串p 的同分异构词,是通过统计二者在所有字母的计数上,都没有相差实现的。如果没有 differ,那么每次都要遍历 count 数组。如果有 differ , 那么 differ 为 0 就表示没有差异。

过程:

初始窗口统计 l=0 r=0 

  • count 数组统计 s[0~pLen] 与 p[0~pLen] 的差异:++count[s.charAt(r)-'a'] --[p.charAt(r)-'a'] r++
  • if (count[i]!=0) differ++ 统计有差异的字母数量。如果这时 differ==0,则位置0可以加入到结果中

窗口滑动过程:

字符 s[l] 要去掉

    • 如果 count 数组中这个字符的差异计数为 1,去掉后正好为0,differ--;
    • 如果 count 数组中这个字符的差异计数为 0,去掉后为 -1 不为 0,differ++;
    • 把 count 数组中这个字符的计数 -1,--count[s.charAt(l)-'a']
    • l++

字符 s[r] 要加进来

    • 如果 count 数组中这个字符的差异计数为 -1,加进来或正好为0,differ--;
    • 如果 count 数组中这个字符的差异计数为 0,加进来后为 1 不为 0,differ++;
    • 把 count 数组中这个字符的计数 +1,++count[s.charAt(r)-'a']
    • r++

如果 differ 为 0,则将 l (同分异构词在 s 中的起始位置)加入到结果 ans 中

class Solution {
    public List<Integer> findAnagrams(String s, String p) {
       int sLen = s.length();
       int pLen = p.length();
       if (sLen < pLen) {
           return new ArrayList<Integer>();
       }
       List<Integer> ans = new ArrayList<Integer>();
       int[] count = new int[26];
       int l=0;
       int r=0;
       // 刚开始的窗口是 s 上的 0~pLen,统计初始窗口每个字母的差异
       for (r=0;r<pLen;r++) {
           // 计数 s 的
           ++count[s.charAt(r)-'a'];
           // p 有相同的就会抵消
           --count[p.charAt(r)-'a'];
       }
        // 现在 l=0 r=pLen
       int differ=0;
       
       // 统计刚开始窗口的 differ(累加 count 数组)
       for (int i=0;i<26;i++) {
           if (count[i]!=0) {
               // 例如 s=aa p=bb count=[2,-2,0,0...] 
               // 这时 differ 应该是 4
               //differ+=Math.abs(count[i]);
               //----------------分割线-------------------------------
               // 不是的。differ 统计的是差的字母数。
                // 比如多两个a,那么differ加一,少四个b,diifer也加一。为0时,differ才减一
               ++differ;
           }
       }
       // 刚开始的窗口 s 上的 0~pLen, 就符合条件
       if (differ == 0) {
           ans.add(0);
       }
       
       // differ 的引入:不用每次都遍历count数组看是否全0
       while (r<sLen) {
           // 把 s.charAt(l) 减去。
           // 原来count[s.charAt(l)-'a']==1 这个字母个数有一个不等的,这个字母一去,就相等了
           if (count[s.charAt(l)-'a']==1) {
               --differ;
           }
           // 原来count[s.charAt(r)-'a']==0 这个字母是相等的,这个字母一去,少了一个,又不等了
           else if (count[s.charAt(l)-'a']==0) {
               ++differ;
           }
           // 注意窗口的移动也会改变 count 数组
           --count[s.charAt(l)-'a'];
           l++;

           // 把 s.charAt(r) 加进来
           if (count[s.charAt(r)-'a']==-1) {
               --differ;
           }
           else if (count[s.charAt(r)-'a']==0) {
               ++differ;
           }
           ++count[s.charAt(r)-'a'];
           r++;
if (differ == 0) { ans.add(l); } } return ans; } }

 

30. 串联所有单词的子串

和上面的同分异构词类似,不同点:

  • l 和 w 每次滑动的步长为单词长,而不是1
  • map 来统计差异,key:word value:这个word在当前窗口和words数组中的差异个数

下面这个错误例子,说明了 start 不只可以从 0 开始

所以要在外层加一层循环,start 从 0 到单词长

 

class Solution {
    public List<Integer> findSubstring(String s, String[] words) {
        List<Integer> ans = new ArrayList<>();
        // 每个单词的长度
        Integer wl = words[0].length();
        // 单词的个数
        Integer wn = words.length;
        // words[]串联单词串的长度
        Integer allWl = wl*wn;
        if (s.length() < allWl) {
            return ans;
        }

        // l 不单可以从 0 开始,还可以从 0~wl 有 wl 种划分方式
        // 但是 start 变量不可以直接用 l 变量代替,l 在后面还会变,会影响这里的 for 循环
        // 所以要用一个新变量 start
        for (int start=0;start<wl;start++) {
            
            if (start+allWl > s.length() ) {
                return ans;
            }

            // key:单词
            // value:s中这个单词与 words 中这个单词的数量差
            // 出现在窗口中的单词,每出现一次,相应的值增加 1
            // 出现在words中的单词,每出现一次,相应的值减少 1
            Map<String, Integer> map = new HashMap();
            // int l=0;
            // int r=0;
            // start 从 0~wl
            int l=start;
            int r=start;
            // s与words 有数量差异的单词 的 单词数
            int differ=0;

            // 初始窗口相关的计算 l~l+wl
            while (r<(l+allWl)) {
                // 用 r 把单词加进来
                String word = s.substring(r, r+wl);
                // 这时还没用 words 中的词抵消。所以都是多 1 
                map.put(word, map.getOrDefault(word, 0) + 1);
                // 一个一个单词的加,所以 r 前进的步长是wl, r从0开始以步长wl前进wn步到allWl 
                r=r+wl;
            }
            for (String word : words) {
                // word 抵消掉一个。如果 s 中没有这个单词->0再减一就变成 -1 代表缺1
                // 如果 s 中有这个单词->1再减一就变成0,二者这个单词的数量一致
                map.put(word, map.getOrDefault(word, 0)-1);
            }
            for (Map.Entry<String, Integer> entry : map.entrySet()) {
                // differ 累计 s与words有数量差异的单词 的 单词数
                if (entry.getValue() != 0) {
                    differ++;
                }
            }
            // 可能刚开始那段就是
            if (differ == 0) {
                ans.add(l);
            }

            // 不超过
            while (r+wl <= s.length()) {
                // 要移走的单词
                // 左指针这个单词是去掉的,map 里之前一定有
                // 所以直接用 get 而不是 getOrDefualt
                String wordRemove = s.substring(l, l+wl);
                if(map.get(wordRemove) == 1) {
                    differ--;
                }
                if(map.get(wordRemove) == 0) {
                    differ++;
                }
                // 移走这个单词,这个单词的相差个数-1
                map.put(wordRemove, map.get(wordRemove)-1);
                l=l+wl;

                // 右指针这个单词是新加进来的,map 里之前可能没有
                // 所以所有 get 都要用 getOrDefualt
                String wordAdd = s.substring(r, r+wl);
                if(map.getOrDefault(wordAdd, 0) == -1) {
                    differ--;
                }
                if(map.getOrDefault(wordAdd, 0) == 0) {
                    differ++;
                }
                // 加入单词,这个单词的相差个数+1
                map.put(wordAdd, map.getOrDefault(wordAdd, 0)+1);
                r=r+wl;

                if (differ == 0) {
                    ans.add(l);
                }
            }
        }
        return ans;
    }
}

 

76. 最小覆盖子串

 和前面的 438.找到字符串中所有字母异位词 解法一样

用滑动窗口,维持当前窗口和字串 t 中的字符数差值

不同的是,前面那个题要求子串连续,滑动窗口的大小是固定的

而这个题要求覆盖即可,滑动窗口的大小是可变的,因此每个循环

  • 如果当前窗口的字符个数可以覆盖子串的,那么 l 右移看可不可以使窗口进一步缩短
  • 如果当前窗口的字符个数不可以覆盖子串的,那么 r 右移看新加进来的字符可不可使当前窗口可以覆盖
  • 用 differ 计没有覆盖的字符数,只要 map 里的 字符差异计数>=0,就是可以覆盖的。<0 没覆盖, differ++

需要注意的是退出循环的条件,并不是 while(r<s.length())

例如:

s = "ADOBECODEBANC", t = "ABC" 答案是 BANC
当 r 指向最后一个的时候,l 还指向的是 2,也就是说当前窗口是 OBECODEBANC, 可以覆盖
但是此时循环还要继续,l 可以继续右移,使窗口进一步缩短,到最小覆盖子串 BANC

过程:

differ = 3 (A B C)

.........r右移

ADOBECODEBANC differ=2 (B C) 

..........r右移

ADOBECODEBANC differ=0

ADOBECODEBANC differ=1 (A)

ADOBECODEBANC differ=1 (A)

...........r右移

ADOBECODEBANC differ=0

ADOBECODEBANC differ=0

...........l左移

ADOBECODEBANC differ=0

ADOBECODEBANC differ=1 (C)

...........r右移

ADOBECODEBANdiffer=0

注意退出循环条件:上面这行 r 已经到末尾了,但是还不能退出循环,因为不是最短的,l要继续左移

...........l左移

ADOBECODEBANdiffer=0

ADOBECODEBANdiffer=1 (B)

dfiffer!=0 && r>=s.length() 退出循环

 

class Solution {
    public String minWindow(String s, String t) {
        Map<Character, Integer> map = new HashMap();
        int l=0;
        int r=0;
        int differ = 0;
        int minLen = Integer.MAX_VALUE;
        String ans = "";
        for (int i=0;i<t.length();i++) {
            char thischar = t.charAt(i);
            map.put(thischar, map.getOrDefault(thischar, 0)-1);
        }
        for (Map.Entry<Character, Integer> entry : map.entrySet()) {
            // 要可以覆盖的话,每个字符的差异 >=0 就可以了(刚好覆盖,或比它多// 所以小于 0,就是没有覆盖到的字符
            // 注意区别:之前是完全字母异位,这里的条件是==0
            if (entry.getValue() < 0) {
                differ++;
            }
        }

        // 条件不能是 while (l<r) 因为lr初始值都是0,开始不了
        while (true) {
            // 有没有覆盖到的字符(map中有字符的value是<0的)
            // r 右移,看新加进来的字符可不可以覆盖到
            if (differ > 0 && r<s.length()) {
                char newchar = s.charAt(r);
                // +1 后变为0,这个字符可以覆盖了
                if (map.getOrDefault(newchar, 0) == -1) {
                    differ--;
                }
                // 如果 map.get(newchar) == 0,+1后为1,>=0就还是能覆盖,不会改变differ
                // 新进来的字符,计数+1
                map.put(newchar, map.getOrDefault(newchar, 0)+1);
                r++;
            }
            // 都覆盖到了(map中所有字符value>=0)
            // l 左移,看窗口可不可以缩得更短
            else if (differ == 0) {
                char removechar = s.charAt(l);
                // 本来刚好覆盖,移走后不能覆盖了
                if (map.get(removechar) == 0) {
                    differ++;
                }
                // 如果 map.get(newchar) == 1,-1后为0,>=0就还是能覆盖,不会改变differ
                // 新移走的字符,计数-1
                map.put(removechar, map.getOrDefault(removechar, 0)-1);
                l++;
            }

            // r>=s.length() r 不能再移了, 以后只会右移 l 缩短窗口了
            // 这时候如果 differ 还不为0,即还有每覆盖到的字符
            // 继续移动l的话,只能去掉字符,不能加入字符,是没有用的
            if (differ != 0 && r>=s.length()) {
                break;
            }
            
            if (differ == 0) {
                int len = r-l;
                if (len<minLen) {
                    minLen = len;
                    ans = s.substring(l, r);
                }
            }
        }
        return ans;
    }
}

 

 

之前没有引入 differ 的解法

class Solution {
    public String minWindow(String s, String t) {
        String res;
        int resStartIndex=0;
        int resEndIndex=0;
        int resShortestLen = Integer.MAX_VALUE;
        Map<Character, Integer> curWinMap = new HashMap();
        // 初始化 map, 放入 t 串每个字符的计数
        for (int i=0;i<t.length();i++) {
            curWinMap.put(t.charAt(i), curWinMap.getOrDefault(t.charAt(i), 0) + 1);
        }
        int l = 0;
        int r = 0;
        while (true) {
            boolean winContainsAllt = winContainsAllt(curWinMap);
            if ((r-l) < resShortestLen && winContainsAllt) {
                resStartIndex = l;
                resEndIndex = r;
                resShortestLen = r-l;
            }

            if (winContainsAllt) {
                char lc = s.charAt(l);
                // 如果已经包含全部了。那么左边的出去,看能不能继续缩短一些
                if (curWinMap.containsKey(lc)) {
                    // 左边的出去了一个可以 cover t 串的,差距计数 +1
                    curWinMap.put(lc, curWinMap.get(lc) + 1);
                }
                l++;
            }
            else {
                // 如果 r 到末尾了,当前窗口可以覆盖,循环还是要继续往下走,看 l 能不能右移使窗口更缩短
                // 如果 r 到末尾了,当前窗口的还不能覆盖,那 l 继续往右走后面更都不能覆盖了,所以可以退出循环了
                if (r == s.length()) {
                    break;
                }
                // 这个要在前面那个 break 的后面,防止数组越界
                char rc = s.charAt(r);
                // 没有包含全部,右边的继续进来新的,看新来的能不能覆盖
                if (curWinMap.containsKey(rc)) {
                    // 右边新进来的这个可以 cover t 串,差距计数 -1
                    curWinMap.put(rc, curWinMap.get(rc) - 1);
                }
                r++;
            }
        }
        return s.subSequence(resStartIndex, resEndIndex).toString();
    }

    private boolean winContainsAllt(Map<Character, Integer> curWinMap) {
        for (Map.Entry<Character, Integer> entry : curWinMap.entrySet()) {
            // 如果不够 cover 掉 t 里面的字符
            // >0 不能 cover; =0 完全 cover ,字符计数相等; <0 完全 cover,并且还有多的
            if (entry.getValue() > 0) {
                return false;
            }
        }
        // 全部 cover 的情况是每一项都 < 0
        return true;
    }
}

 

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

 维持一个滑动窗口,使窗口内始终维持没有重复字符,计算此窗口的最大值

怎么实现滑动窗口内没有重复字符呢?

用一个 map ,记录每个字符上一次出现的位置

每次移动 r ,让右边新字符加进来,根据 map 获得这个新字符上次出现的位置

如果在当前窗口内(即>左指针l),说明在当前窗口内

因此要移动左指针到 上次出现的位置 + 1,把这个重复字符排出当前窗口内

class Solution {

    public int lengthOfLongestSubstring(String s) {
        if (s == null || "".equals(s)) {
            return 0;
        }
        int res = Integer.MIN_VALUE;
        int l = 0;
        int r = 0;
        // 记录字符上一次出现的位置。
        Map<Character, Integer> char2LastIndexMap = new HashMap<>();
        while (r<s.length()) {
            char rc = s.charAt(r);
            Integer rcLastIndex = char2LastIndexMap.get(rc);
            
            // 对于右边新加入的字符,要维持滑动窗口内无重复的
            // 就要看它上次出现的位置是不是在窗口内,也就是说它上次出现的位置 > l左指针
            // 如果在窗口内,就将 l 移动到它上次出现的位置 +1
            if (rcLastIndex != null && rcLastIndex >= l) {
                l = rcLastIndex + 1;
            }
            // 为什么要 + 1,应为这里已经把 r 当成新加进来的了
            if (r-l+1 > res) {
                res = r-l+1;
            }
            char2LastIndexMap.put(rc ,r);
            r++;
        }
        return res;
    }
}

 

209. 长度最小的子数组

minLen 初始值 nums.length+1(一个不可能的大值)

滑动窗口,右指针负责新加进来元素,满组 sum>=target 的条件,就更新 minLen 为当前长度

然后 while(sum<target) 移动左指针,使得 sum 重新小于 target,注意每次移动左指针的时候,进了 while(sum<target) 说明满足这个条件,因此也要更新 minLen

如果最后的 minLen 仍然大于 nums.length,说明仍然为初始值,没有被更新过,没有符合条件的,返回0

public int minSubArrayLen(int target, int[] nums) {
        int minLen = nums.length+1;
        int l = 0;
        int r = 0;
        int sum = 0;
        // 为什么条件里没有 l<=r 
        // 因为使 l 发生移动的都是 sum>=target, l 移动到 < 就好了, 有 sum 在控制,所以不会超过 r
        while (r<nums.length) {
            sum = sum + nums[r];
            if (sum >= target) {
                minLen = Math.min(minLen, r-l+1);
            }
            while (sum >= target) {
                // 能进来这里的一定满足 sum>=target, 所以可以直接更新 minLen
                // 但是注意一定要在 l++ 前, l++ 之后可能就不满足 sum>=target 了,该退出循环了
                minLen = Math.min(minLen, r-l+1);
                // l右移
                sum = sum-nums[l];
                l++;
            }
            r++;
        }
        // 到最后, minLen 仍然是初始值 nums.length+1, 没有被更新, 说明没有符合条件的答案
        if (minLen > nums.length) {
            minLen = 0;
        }
        return minLen;
    }