滑动窗口(数据结构与算法java代码)

第一题 长度最小的子数组

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

找出该数组中满足其总和大于等于 target 的长度最小的 连续子数组

 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度如果不存在符合条件的子数组,返回 0 。

暴力解法(双for循环)

超出时间限制

class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        int result = Integer.MAX_VALUE;
        for (int i = 0; i < nums.length; i++) {
            int sum = 0;
            for (int j = i; j < nums.length; j++) {
                sum += nums[j];
                if (sum >= target) {
                    result = Math.min(result, j - i + 1);
                    break;
                }
            }
        }
        return result == Integer.MAX_VALUE ? 0 : result;
    }
}

滑动窗口(双指针)

r 是窗口的终止位置, l 是窗口的起始位置,不断调节 l 从而将暴力解法的时间复杂度O(n^2)降为O(n)

class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        int result = Integer.MAX_VALUE;
        int sum = 0;
        int l = 0;
        for (int r = 0; r < nums.length; r++) {
            sum += nums[r];
            while (sum >= target) {
                result = Math.min(result, r - l + 1);
                sum -= nums[l++];
            }
        }
        return result == Integer.MAX_VALUE ? 0 : result;
    }
}

第二题 水果成篮

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

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

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

给你一个整数数组 fruits ,返回你可以收集的水果的 最大 数目。

滑动窗口一(哈希表)

用时稍长

通过窗口(map集合)来记录水果种类数及其数量,当出现第三种水果时右移左边界left,并通过窗口左右边界相减求得ans保存前两种水果总数,接着重复操作,直道遍历完毕,求得符合题意的水果数

class Solution {
    public int totalFruit(int[] fruits) {
        int n = fruits.length;
        Map<Integer, Integer> cnt = new HashMap<Integer, Integer>();
        int left = 0, ans = 0;
        for (int right = 0; right < n; ++right) {
            cnt.put(fruits[right], cnt.getOrDefault(fruits[right], 0) + 1);
            // 水果种类数多于两种时,减去其中第一个出现的,并右移left指针
            while (cnt.size() > 2) {
                cnt.put(fruits[left], cnt.get(fruits[left]) - 1);
                if (cnt.get(fruits[left]) == 0) {
                    cnt.remove(fruits[left]);
                }
                ++left;
            }
            ans = Math.max(ans, right - left + 1);
        }
        return ans;
    }
}

滑动窗口二(指针)

l 是窗口的起始位置,r 是窗口的结束位置,窗口记录了第一种水果的数量 r + 1,并通过代码的执行顺序记录下前两种水果总数 i - l ,当遇到第三种水果时更新窗口,依次循环,for循环结束时记录下了除倒一和倒二类型的水果总数外的符合题意的最多水果数,在函数返回时再与最后两种水果的数量和作比较

class Solution {
    public int totalFruit(int[] fruits) {
        int rs = 0;
        int l = 0, r = -1;
        for (int i = 1; i < fruits.length; i++) {
            if (fruits[i] == fruits[i - 1]) {
                continue;
            }
            //当出现第三种水果时,记录前两种水果总个数
            if (r != -1 && fruits[i] != fruits[r]) {
                rs = Math.max(rs, i - l);
                //更新第一个水果第一次出现的位置
                l = r + 1;
            }
            //更新第一种水果最后出现的位置
            r = i - 1;
        }
        return Math.max(fruits.length - l, rs);
    }
}

第三题 最小覆盖子串

 点击跳转到力扣

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

滑动窗口(哈希表)

用时较长

在 s 上滑动窗口,通过移动 r 指针不断扩张窗口,当窗口可行时,再移动 l 指针收缩窗口

class Solution {
    // ori -> t cnt -> s
    Map<Character, Integer> ori = new HashMap<Character, Integer>();
    Map<Character, Integer> cnt = new HashMap<Character, Integer>();

    public String minWindow(String s, String t) {
        // 通过ori集合记录t中的字母及其个数
        for (int i = 0; i <  t.length(); i++) {
            ori.put(t.charAt(i), ori.getOrDefault(t.charAt(i), 0) + 1);
        }
        int l = 0, r = -1;
        int len = Integer.MAX_VALUE, ansL = -1, ansR = -1;
        while (r < s.length()) {
            ++r;
            // 如果ori集合中包含s字符串r索引上的字符,将该字符及其出现次数记录在cnt集合中
            if (r < s.length() && ori.containsKey(s.charAt(r))) {
                cnt.put(s.charAt(r), cnt.getOrDefault(s.charAt(r), 0) + 1);
            }
            // 如果当前窗口可行的,进入循环收缩左窗并记录可行的最小窗口;不可行,继续扩张右窗
            while (check() && l <= r) {
                if (r - l + 1 < len) {
                    // 更新可行窗口长度,此窗口的左窗和右窗索引
                    len = r - l + 1;
                    ansL = l;
                    ansR = l + len;
                }
                // 如果左窗的字母被ori包含,那么左窗右移,cnt去掉一个该字母
                if (ori.containsKey(s.charAt(l))) {
                    cnt.put(s.charAt(l), cnt.getOrDefault(s.charAt(l), 0) - 1);
                }
                ++l;
            }
        }
        // 遍历完毕,若无可行窗口,返回 -1,有则截取可行窗口,即s的子字符串返回
        return ansL == -1 ? "" : s.substring(ansL, ansR);
    }

    public boolean check() {
        // 迭代器iter遍历ori集合
        Iterator iter = ori.entrySet().iterator(); 
        while (iter.hasNext()) { 
            // 通过键值对对象获取ori集合的键和值,即字母及其个数
            Map.Entry entry = (Map.Entry) iter.next(); 
            Character key = (Character) entry.getKey(); 
            Integer val = (Integer) entry.getValue(); 
            // 如果cnt集合中没有该字母或字母个数少于ori集合的,返回false
            if (cnt.getOrDefault(key, 0) < val) {
                return false;
            }
        }
        // 如果当前cnt集合已经符合条件了,返回true
        return true;
    }
}

滑动窗口(指针)

比哈希表快,也多了一些判断

class Solution {
    public String minWindow(String s, String t) {
        if (s == null || s == "" || t == null || t == "" || s.length() < t.length()) {
            return "";
        }
        //已有字符串指定字符的出现次数 目标字符串指定字符的出现次数
        //ASCII表总长128
        int[] need = new int[128];
        int[] have = new int[128];

        //记录目标字符串指定字符的出现次数
        for (int i = 0; i < t.length(); i++) {
            need[t.charAt(i)]++;
        }

        //左指针 右指针 最小长度(初始值为一定不可达到的长度)
        //已有字符串中目标字符串指定字符的出现总频次 最小覆盖子串在原字符串中的起始位置
        int left = 0, right = 0, min = s.length() + 1, count = 0, start = 0;
        while (right < s.length()) {
            char r = s.charAt(right);
            if (need[r] == 0) {
                right++;
                continue; // 无目标字符,跳
            }
            //目标字符数目大于1时判断
            if (have[r] < need[r]) {
                count++;
            }
            have[r]++;
            right++;
            //已有字符串已经包含了所有目标字符串的字符
            while (count == t.length()) {
                //挡窗口的长度比已有的最短值小时,更改最小值,并记录起始位置
                if (right - left < min) {
                    min = right - left;
                    start = left;
                }
                char l = s.charAt(left);
                //如果左边即将要去掉的字符不被目标字符串需要,那么不需要多余判断,直接可以移动左指针
                if (need[l] == 0) {
                    left++;
                    continue;
                }
                //如果左边即将要去掉的字符被目标字符串需要,且出现的频次正好等于指定频次,那么如果去掉了这个字符,
                //就不满足覆盖子串的条件,此时要破坏循环条件跳出循环,即控制目标字符串指定字符的出现总频次(count)-1
                if (have[l] == need[l]) {
                    count--;
                }
                //已有字符串中目标字符出现的次数-1
                have[l]--;
                //移动左指针
                left++;
            }
        }
        return min == s.length() + 1? "" : s.substring(start, start + min);

  • 9
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
滑动窗口算法(Sliding Window Algorithm)是一种常用的算法技巧,用于解决一些数组或字符串相关的问题。它通过维护一个固定大小的窗口,并在窗口滑动的过程中对窗口中的元素进行处理,从而得到问题的解。 下面是一个使用滑动窗口算法解决问题的示例,使用Java语言实现: ```java public class SlidingWindowAlgorithm { public static void main(String[] args) { int[] nums = {2, 4, 1, 5, 3, 2, 7, 1}; int targetSum = 8; int result = findTargetSum(nums, targetSum); System.out.println("Result: " + result); } public static int findTargetSum(int[] nums, int targetSum) { int windowSum = 0; // 窗口内的元素和 int windowStart = 0; // 窗口的起始位置 int minLength = Integer.MAX_VALUE; // 记录最小子数组长度 for (int windowEnd = 0; windowEnd < nums.length; windowEnd++) { // 窗口右移,加上当前元素 windowSum += nums[windowEnd]; // 当窗口内元素和大于等于目标和时,缩小窗口 while (windowSum >= targetSum) { // 更新最小子数组长度 minLength = Math.min(minLength, windowEnd - windowStart + 1); // 窗口左移,减去左边界元素 windowSum -= nums[windowStart]; windowStart++; } } return minLength != Integer.MAX_VALUE ? minLength : 0; } } ``` 以上示例中,我们使用滑动窗口算法来寻找数组中和大于等于目标和的最小子数组长度。通过维护一个窗口,不断向右移动,并根据窗口内元素和与目标和的比较来调整窗口的大小。最后返回最小子数组长度。 希望以上示例能帮助到您理解滑动窗口算法的使用。如果您有其他问题,请随时提问。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值