滑动窗口算法学习(一)

目录

思想

案例

连续元素最大之和

题目

分析

解法

求相同字母异序词的下标

题目

分析

解法

 


思想

滑动窗口是指一个窗口,在一段区间上从左到右滑动,一直到区间的尾部,滑动窗口的长度是可以动态变化的(也可以固定)。一般情形下可以使用两个指针分别指向窗口的两边,指针 i 指向窗口的左边界,指针 j 指向窗口的右边界

一些情况下,使用滑动窗口处理字符串和数组 能提高效率,减少循环的嵌套使用,降低时间复杂度

滑动窗口在网络、大数据、流量控制等都有应用。

下面将简单介绍使用滑动窗口处理字符串和数组的案例

案例

连续元素最大之和

题目

有一个整型数组,给定一个正整数k,求数组里连续k个数之和的最大值

Input : arr[] = {100, 200, 300, 400}  k = 2

Output : 700

Input : arr[] = {1, 4, 2, 10, 23, 3, 1, 0, 20}  k = 4

Output : 39

Input : arr[] = {2, 3} k = 3 Output : Invalid 

分析

上述问题能想到的最简单的解法就是遍历整个数组,从下标为0开始,每次循环获取k个连续的元素,计算其和之后比较获得最大的和,这样一来需要两次循环(一层遍历数组,一层遍历k个元素)。

我们可以使用滑动窗口来简化代码,因为k是给定的固定值,所以窗口大小是固定的,相当于窗口向右滑动的时候,只需要减去左侧的值(被移除窗口范围的旧值),再加上右侧的值(新移入窗口范围的元素),就可以得到新的连续K个数之和,遍历之后就能得到最大值,只需要一层遍历就足够了

解法

public void cal(int[] array, int k) {
        if (array.length == 0 || k <= 0 || k > array.length) {
            System.out.println("Invalid");
            return;
        }

        int maxSum = 0;
        //计算初始窗口位置的K个数之和
        for (int i = 0; i < k; i++) {
            maxSum += array[i];
        }

        int windows_sum = maxSum;
        for (int i = 1; i <= array.length - k; i++) {
            windows_sum = windows_sum - array[i - 1] + array[k + i - 1];
            if (windows_sum > maxSum) {
                maxSum = windows_sum;
            }
        }

        System.out.println("result= " + maxSum);

    }

求相同字母异序词的下标

题目

给定一个字符串s,和一个非空字符串p,要求寻找在s中所有和p满足相同字母异序词的子串(s,p全都是由26个小写英文字母组成,并且长度不大于20100),并返回对应的子串的第一个字符的下标

Input: s: "cbaebabacd" p: "abc"

Output: [0, 6]

分析

要求找到相同字母异序词,只需要子串里各个字母的个数相同即可。

1.使用map集合来表示子串,key为26个英文字母,value为每个字母对应的个数

2.使用int数组来表示子串(每个元素表示对应字母的个数,第一个元素表示’a’的个数,第二个表示’b’的个数,以此类推…),每次滑动的时候判断目标窗口和滑动窗口里的int数组是否一致

解法

解法一:

使用map里记录每个子串里的每个字母的元素个数,由于采用判断map是否有该元素的方式来加快遍历速度,故每次判断字母后需要减去1,每结束一次循环判断还需要还原现场(把map还原成初始状态initMap)

 public static List<Integer> findAnagrams(String s, String p) {
        List<Integer> result = new ArrayList<Integer>();
        if (s == null || p == null || p.length() > s.length()) {
            return result;
        }

        Map<Character, Integer> map = new HashMap<Character, Integer>();
        initMap(p, map);

        int windowsLen = p.length();
        //代表左右窗口指针
        int left = 0;
        int right = left + windowsLen - 1;

        while (right < s.length()) {
            boolean flag = true;
            List<Character> list = new ArrayList<Character>();
            for (int i = right; i >= left; i--) {
                char c = s.charAt(i);
                Integer count = map.get(c);
                //i对应的字母不在目标串里,因为两个子串的长度一致,故所有包含i上的字母的子串都不满足条件
                if (count == null || count == 0) {
                    left = i + 1;
                    right = left + windowsLen - 1;
                    flag = false;
                    break;
                } else {
                    map.put(c, map.getOrDefault(c, 0) - 1);
                    //把在比较过程中满足的字母存在list里(因为这里在比较字符串的时候采用相减的方式而不是==)
                    list.add(c);
                }
            }
            //如果退出时,flag表示整个窗口的元素都在map里,说明满足条件
            if (flag == true) {
                result.add(left);
                left++;
                right = left + windowsLen - 1;
            }
            //还原现场,让map的数据保持和p的组成一致
            for (Character c : list) {
                map.put(c, map.getOrDefault(c, 0) + 1);
            }

        }

        return result;
    }

    private static void initMap(String p, Map<Character, Integer> map) {
        map.clear();
        for (char c : p.toCharArray()) {
            map.put(c, map.getOrDefault(c, 0) + 1);
        }
    }

 

解法二:

上述代码比较繁琐,解法二使用int数组,直接比较数组,逻辑上更加清晰

public List<Integer> findAnagrams(String s, String p) {
        List<Integer> ans = new ArrayList<>();
        if (s.length() < p.length()) {
            return ans;
        }

        int[] dict = new int[26];
        //使用26长度的整型数组来表示给定字符串p的组成
        for (char c : p.toCharArray()) {
            dict[c - 'a']++;
        }
        //用来记录滑动窗口里的字符串的组成
        int[] cur = new int[26];
        for (int i = 0; i < p.length() ; i++) {
            cur[s.charAt(i) - 'a']++;
        }

        for (int i = p.length() ; i < s.length(); i++) {
            if (isSame(dict, cur)) {
                ans.add(i - p.length() );
            }
            cur[s.charAt(i - p.length() ) - 'a']--;
            cur[s.charAt(i) - 'a']++;
        }
        return ans;
    }
     //判断两个int数组是否一致
     private boolean isSame(int[] a, int[] b) {
        for (int i = 0; i < 26; i++) {
            if (a[i] != b[i]) {
                return false;
            }
        }
        return true;
    }

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值