代码随想录-数组-长度最小的子数组(滑动窗口)及相关题目(JS)

209.长度最小的子数组

题目

给定一个含有 n 个正整数的数组和一个正整数 target 。

找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, …, numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。

示例 1:

输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。
示例 2:

输入:target = 4, nums = [1,4,4]
输出:1
示例 3:

输入:target = 11, nums = [1,1,1,1,1,1,1,1]
输出:0

提示:

1 <= target <= 10^9
1 <= nums.length <= 10^5
1 <= nums[i] <= 10^5

方法

本题是滑动窗口

先移动右边指针(即末尾元素),直到sum >= target;然后再移动左边指针,取最小长度。

【整理时和动态规划那几道求长度的题目对比一下区别】

代码

/**
 * @param {number} target
 * @param {number[]} nums
 * @return {number}
 */
var minSubArrayLen = function(target, nums) {
    let res = Infinity;
    let left = 0;
    let sum = 0;
    for (let right = 0;right < nums.length;right++){
        sum += nums[right];
        while (sum >= target){
            res = Math.min(res,right - left + 1);
            sum -= nums[left];
            left++;
        }
    } 
    return res == Infinity ? 0 : res;
};

不要光靠回忆做题。要认真审题,抓住关键点,认真分析。

904.水果成篮

题目

你正在探访一家农场,农场从左到右种植了一排果树。这些树用一个整数数组 fruits 表示,其中 fruits[i] 是第 i 棵树上的水果 种类 。

你想要尽可能多地收集水果。然而,农场的主人设定了一些严格的规矩,你必须按照要求采摘水果:

你只有 两个 篮子,并且每个篮子只能装 单一类型 的水果。每个篮子能够装的水果总量没有限制。
你可以选择任意一棵树开始采摘,你必须从 每棵 树(包括开始采摘的树)上 恰好摘一个水果 。采摘的水果应当符合篮子中的水果类型。每采摘一次,你将会向右移动到下一棵树,并继续采摘。
一旦你走到某棵树前,但水果不符合篮子的水果类型,那么就必须停止采摘。
给你一个整数数组 fruits ,返回你可以收集的水果的 最大 数目。

示例 1:

输入:fruits = [1,2,1]
输出:3
解释:可以采摘全部 3 棵树。
示例 2:

输入:fruits = [0,1,2,2]
输出:3
解释:可以采摘 [1,2,2] 这三棵树。
如果从第一棵树开始采摘,则只能采摘 [0,1] 这两棵树。
示例 3:

输入:fruits = [1,2,3,2,2]
输出:4
解释:可以采摘 [2,3,2,2] 这四棵树。
如果从第一棵树开始采摘,则只能采摘 [1,2] 这两棵树。
示例 4:

输入:fruits = [3,3,3,1,2,1,1,2,3,3,4]
输出:5
解释:可以采摘 [1,2,1,1,2] 这五棵树。

提示:

1 <= fruits.length <= 10^5
0 <= fruits[i] < fruits.length

方法

我们可以使用滑动窗口解决本题,left 和 right 分别表示满足要求的窗口的左右边界,同时我们使用哈希表存储这个窗口内的数以及出现的次数。

我们每次将 right 移动一个位置,并将 fruits[right] 加入哈希表。如果此时哈希表不满足要求(即哈希表中出现超过两个键值对),那么我们需要不断移动 left,并将 fruits[left] 从哈希表中移除,直到哈希表满足要求为止。

需要注意的是,将fruits[left] 从哈希表中移除后,如果 fruits[left] 在哈希表中的出现次数减少为 0,需要将对应的键值对从哈希表中移除。

作者:LeetCode-Solution

代码

/**
 * @param {number[]} fruits
 * @return {number}
 */
var totalFruit = function(fruits) {
    let left = 0,res = 0;
    let cnt = new Map();
    for (let right = 0;right < fruits.length;right++){
        cnt.set(fruits[right],(cnt.get(fruits[right]) || 0) + 1);
        while (cnt.size > 2){
            cnt.set(fruits[left],cnt.get(fruits[left]) - 1);
            if (cnt.get(fruits[left]) == 0){
                cnt.delete(fruits[left]);
            }
            left++
        }
        res = Math.max(res,right - left + 1);
    }
    return res;
};

不算难,主要自己对JS的数据结构不熟悉

Map的数据结构

存入数据:map.set(a,b);

获取数据:map.get(a) || 0 ;

在map中搜索元素a,若能找到则返回value,若找不到则返回undefined

删除数据:map.delet(a);

数据的长度:map.size 【没有括号】

复杂度分析

时间复杂度:O(n),其中 n 是数组 fruits 的长度。

空间复杂度:O(1)。哈希表中最多会有三个键值对,可以看成使用了常数级别的空间。

76.最小覆盖子串

题目

给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 “” 。

注意:

对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
如果 s 中存在这样的子串,我们保证它是唯一的答案。

示例 1:

输入:s = “ADOBECODEBANC”, t = “ABC”
输出:“BANC”
解释:最小覆盖子串 “BANC” 包含来自字符串 t 的 ‘A’、‘B’ 和 ‘C’。
示例 2:

输入:s = “a”, t = “a”
输出:“a”
解释:整个字符串 s 是最小覆盖子串。
示例 3:

输入: s = “a”, t = “aa”
输出: “”
解释: t 中两个字符 ‘a’ 均应包含在 s 的子串中,
因此没有符合条件的子字符串,返回空字符串。

提示:

m == s.length
n == t.length
1 <= m, n <= 10^5
s 和 t 由英文字母组成

方法

本问题要求我们返回字符串 s 中包含字符串 t 的全部字符的最小窗口。我们称包含 t 的全部字母的窗口为「可行」窗口。

我们可以用滑动窗口的思想解决这个问题。在滑动窗口类型的问题中都会有两个指针,一个用于「延伸」现有窗口的 r 指针,和一个用于「收缩」窗口的 l指针。在任意时刻,只有一个指针运动,而另一个保持静止。我们在 s上滑动窗口,通过移动 r指针不断扩张窗口。当窗口包含 t 全部所需的字符后,如果能收缩,我们就收缩窗口直到得到最小窗口。

本题目的关键在于如何确定r指针扩张的窗口正好包含t全部所需字符

本题建立两个哈希表,抽取一个函数用来比较r指针移动下的哈希表是否能够覆盖t字符串的哈希表

作者:LeetCode-Solution

代码

Java

 public String minWindow(String s, String t) {
        HashMap<Character, Integer> map1 = new HashMap<>();
        HashMap<Character, Integer> map2 = new HashMap<>();
        for (int i = 0; i < t.length(); i++) {
            map2.put(t.charAt(i), map2.getOrDefault(t.charAt(i), 0) + 1);
        }
        int left = 0;
        int minLength = Integer.MAX_VALUE;
        int key1 = 0, key2 = 0;
        for (int right = 0; right < s.length(); right++) {
            map1.put(s.charAt(right), map1.getOrDefault(s.charAt(right), 0) + 1);
            // 判断覆盖是否成功
            boolean judgeCover = judgeCover(map1, map2);
            // 一旦成功 试图进行滑动窗口缩减
            while (judgeCover) {
                if (minLength > right - left + 1) {
                    minLength = right - left + 1;
                    key1 = left;
                    key2 = right;
                }
                // 缩减map中的left字符,然后判断字符数量是否等于0,如果等于0就移除
                map1.put(s.charAt(left), map1.get(s.charAt(left)) - 1);
                if (map1.get(s.charAt(left)) == 0) {
                    map1.remove(s.charAt(left));
                }
                left++;
                // 每次缩减都去重新判断是否覆盖 如果覆盖失败,则继续right++,left不动
                judgeCover = judgeCover(map1, map2);
            }
        }
        return minLength == Integer.MAX_VALUE ? "" : s.substring(key1, key2 + 1);
    }

    /**
     * 每次去判断是否已经产生覆盖的情况
     * @param map1
     * @param map2
     * @return
     */
    public boolean judgeCover(HashMap<Character, Integer> map1, HashMap<Character, Integer> map2) {
        Set<Map.Entry<Character, Integer>> entries = map2.entrySet();
        for (Map.Entry<Character, Integer> entry : entries) {
            Character key = entry.getKey();
            if (!map1.containsKey(key)) {
                return false;
            } else {
                if (entry.getValue() > map1.get(key)) {
                    return false;
                }
            }
        }
        return true;
    }

Javascript

/**
 * @param {string} s
 * @param {string} t
 * @return {string}
 */
var minWindow = function(s, t) {
    let sMap = new Map();
    let tMap = new Map();
    let key1 = 0,key2 = 0;
    let minLenth = Infinity;
    // 用来缩小范围的指针
    let left = 0;
    for (let i = 0;i < t.length;i++){
        tMap.set(t[i],(tMap.get(t[i]) || 0) + 1);
    }
    for (let right = 0;right < s.length;right++){
        sMap.set(s[right],(sMap.get(s[right]) || 0) + 1);
        while (judgetCover(sMap,tMap)){
            // 目前已加入的 s 能够覆盖 t 字符串进行如下操作
            if (minLenth > right - left + 1){
                minLenth = right - left + 1;
                key1 = left;
                key2 = right;
            }
            sMap.set(s[left],sMap.get(s[left]) - 1);
            if (sMap.get(s[left]) == 0){
                sMap.delete(s[left]);
            }
            left++;
        }
    }
    return minLenth == Infinity ? "" : s.substring(key1,key2+1);
};
// forEach一直报错(不太懂),改成for...of 好了
var judgetCover = function(sMap, tMap){
    // tMap.forEach(function(value,key){
    //     if (!sMap.has(key)){
    //         return false;
    //     } else {
    //         if (value > sMap.get(key)){
    //             return false;
    //         }
    //     }
    // });
    // return true;
    for (let x of tMap){
        if (!sMap.has(x[0])){
            return false;
        } else {
            if (x[1] > sMap.get(x[0])){
                return false;
            }
        }
    }
    return true;
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值