• LeetCode 1297. 子串的最大出现次数


    题目描述

    给你一个字符串 s ,请你返回满足以下条件且出现次数最大的 任意 子串的出现次数:

    • 子串中不同字母的数目必须小于等于 maxLetters 。
    • 子串的长度必须大于等于 minSize 且小于等于 maxSize

    思路

    一.滑窗+穷举+去重

    根据第一个条件:子串中不同字母的数量必须<= maxLetters,可以想到用滑窗。那么我们用一个滑窗,从左到右扫描这个字符串,只要遇到窗口内的不同字母数<= maxLetters,就停下来,扫描窗口内的全部长度介于[minSize, maxSize]的子串(穷举所有长度满足条件的子串),加入到一个用于计数的Map中,并实时更新答案即可。注意,滑动窗口移动的过程中,窗口内的子串可能会被重复添加,所以需要去重,去重的标准是子串的起始位置[l,r]。只有当某组[l,r]没有被添加过时,才添加该子串。

    /**
    * 553ms 14%
    * 223MB 
    **/
    class Solution {
    
        int BASE = 1_000_00;
    
        public int maxFreq(String s, int maxLetters, int minSize, int maxSize) {
            // HashTable + SlideWindow
            Map<String, Integer> freqMap = new HashMap<>();
            Set<Long> st = new HashSet<>(); // 用来去重
            int ans = 0;
            int[] cnt = new int[26]; // 计数
            int kind = 0; // 字母种类个数
            char[] cs = s.toCharArray();
    
            for (int i = 0, j = 0; i < cs.length; i++) {
                int u = cs[i] - 'a';
                cnt[u]++;
                if (cnt[u] == 1) kind++;
                // 先收缩窗口的左边界, 将字符种类数满足条件
                while (kind > maxLetters) {
                    u = cs[j] - 'a';
                    cnt[u]--;
                    if (cnt[u] == 0) kind--;
                    j++;
                }
    
                // 在[j, i]区间内, 寻找全部长度满足[minSize, maxSize]的子串
                for (int begin = j; begin + minSize - 1 <= i; begin++) {
                    for (int end = begin + minSize - 1; end <= i; end++) {
                        long hash = begin * BASE + end; // 将区间[begin, end] 进行序列化并保存
                        // 这个区间的子串已经被扫描过, 跳过
                        if (st.contains(hash)) continue;
                        st.add(hash);
                        String sub = s.substring(begin, end + 1);
                        freqMap.put(sub, freqMap.getOrDefault(sub, 0) + 1);
                        ans = Math.max(ans, freqMap.get(sub));
                    }
                }
            }
            return ans;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    由于每次找到一个字母种类数满足条件的区间,就会对区间内进行全部扫描,会有很多区间被重复扫描过,效率比较低。

    O ( n 2 × S 2 ) O(n^2 \times S^2 ) O(n2×S2),外层滑窗的循环,复杂度为 O ( n ) O(n) O(n),每找到一个区间后,扫描全部子串的复杂度为 O ( n × S 2 ) O(n \times S^2) O(n×S2)

    其中 n n n表示字符串长度, S S S 表示 minSizemaxSize的数量级,在本题中是26

    二.滑窗+固定求解长度为minSize的子串

    看了题解后,发现可以进行优化。假设满足条件,且出现次数最多的子串为T,那么T的子串中,若有满足条件的,出现次数与T相同,但长度比T短。也就是说,只要能找到一个T,是出现次数最多的子串,那么一定能找到T的某个子串,出现次数也一样多。而T的子串中,要满足条件,其长度一定要满足maxSize >= len >= minSize,所以我们可以只取长度为minSize的子串进行求解即可。

    同样借助滑窗,代码优化如下:

    /**
    * 27ms 86%
    * 43.5MB
    **/
    class Solution {
        public int maxFreq(String s, int maxLetters, int minSize, int maxSize) {
            int ans = 0, n = s.length();
            char[] cs = s.toCharArray();
            int[] cnt = new int[26];
            Map<String, Integer> map = new HashMap<>();
            int kind = 0;
            
            for (int i = 0, j = 0; i < n; i++) {
                int u = cs[i] - 'a';
                cnt[u]++;
                if (cnt[u] == 1) kind++;
                
                // 先让字母种类数满足条件, 然后让长度减小到minSize
                while (kind > maxLetters || i - j + 1 > minSize) {
                    u = cs[j] - 'a';
                    cnt[u]--;
                    if (cnt[u] == 0) kind--;
                    j++;
                }
    
                // 若长度为minSize, 则记录答案
                if (i - j + 1 == minSize) {
                    String sub = s.substring(j, i + 1);
                    map.put(sub, map.getOrDefault(sub, 0) + 1);
                    ans = Math.max(ans, map.get(sub));
                }
            }
            return ans;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    substring截取子串的复杂度为 O ( 1 ) O(1) O(1)的话,总的时间复杂度 O ( n ) O(n) O(n),因为只需要一次滑窗。上面

    的思路,可以这样理解,相当于尝试找到以每个位置作为结尾,且满足条件(字母种类<=maxLetters以及长度为minSize)的子串。

    三.枚举全部长度为minSize的子串

    由于只需要找长度为minSize的子串,一个简单的想法就是,枚举全部长度为minSize的子串,并用一个Set来统计其中的字符种类。

    /**
    * 53ms 41%
    * 44MB
    **/
    class Solution {
        public int maxFreq(String s, int maxLetters, int minSize, int maxSize) {
            Map<String, Integer> map = new HashMap<>();
            Set<Character> set = new HashSet<>();
            int n = s.length();
            char[] cs = s.toCharArray();
            int ans = 0;
            for (int i = 0; i + minSize - 1 < n; i++) {
                int end = i + minSize - 1;
                for (int j = i; j <= end; j++) set.add(cs[j]);
                if (set.size() <= maxLetters) {
                    String ss = s.substring(i, end + 1);
                    map.put(ss, map.getOrDefault(ss, 0) + 1);
                    ans = Math.max(ans, map.get(ss));
                }
                set.clear();
            }
            return ans;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    时间复杂度 O ( n × S ) O(n \times S) O(n×S),遍历全部子串需要 O ( n ) O(n) O(n),统计每个子串的字符数量需要 O ( S ) O(S) O(S)

    四.滑窗+字符串哈希

    假设substring的时间复杂度不为 O ( 1 ) O(1) O(1),那么我们可以采用字符串哈希的方式,在 O ( 1 ) O(1) O(1)的复杂度下求得一个子串(的唯一表示)

    /**
    * 25ms 92%
    * 44MB
    **/
    class Solution {
        long P = 131L;
        long[] hash;
        long[] p;
    
        public int maxFreq(String s, int maxLetters, int minSize, int maxSize) {
            Map<Long, Integer> map = new HashMap<>();
            // 字符串哈希
            int n = s.length();
            hash = new long[n + 1];
            p = new long[n + 1];
            p[0] = 1;
            for (int i = 0; i < n; i++) {
                char c = s.charAt(i);
                hash[i + 1] = hash[i] * P + c; // 溢出即取模
                p[i + 1] = p[i] * P;
            }
    
            int[] cnt = new int[26];
            int kind = 0, ans = 0;
            for (int i = 0, j = 0; i < n; i++) {
                char c = s.charAt(i);
                int u = c - 'a';
                cnt[u]++;
                if (cnt[u] == 1) kind++;
                while (kind > maxLetters || i - j + 1 > minSize) {
                    c = s.charAt(j);
                    u = c - 'a';
                    cnt[u]--;
                    if (cnt[u] == 0) kind--;
                    j++;
                }
                if (i - j + 1 == minSize) {
                    // 计算 [j, i] 之间的字符串哈希
                    // 对应的区间为 [j + 1, i + 1]
                    long t = hash[i + 1] - hash[j] * p[minSize];
                    map.put(t, map.getOrDefault(t, 0) + 1);
                    ans = Math.max(ans, map.get(t));
                }
            }
            return ans;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    时间复杂度为 O ( n ) O(n) O(n),预处理 O ( n ) O(n) O(n),滑窗 O ( n ) O(n) O(n)

  • 相关阅读:
    ROS2 中的轻量级、自动化、受控回放
    llama2+localGPT打造纯私有知识助手
    Android Graphics 显示系统 - 如何模拟多(物理)显示屏?
    Vue模板语法下集(03)
    实践自定义弹框列表数据 slot 操作数据
    《大话设计模式》精髓理解——Chapter 16 - 20 状态、适配器、备忘录、组合、迭代器
    快速排序压缩算法2024年最新一种压缩算法
    SRDenseNet
    ps安装遇到问题
    基于java+springboot+vue实现的高校社团管理系统(文末源码+Lw+ppt)23-419
  • 原文地址:https://blog.csdn.net/vcj1009784814/article/details/126251013