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;
    }
}

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

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;
    }
}

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;
    }
}

时间复杂度 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;
    }
}

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据提供的引用内容,有三种方法可以解决LeetCode上的最长回文问题。 方法一是使用扩展中心法优化,即从左向右遍历字符,找到连续相同字符组成的作为扩展中心,然后从该中心向左右扩展,找到最长的回文。这个方法的时间复杂度为O(n²)。\[1\] 方法二是直接循环字符,判断是否是回文,然后得到最长回文。这个方法的时间复杂度为O(n³),效率较低。\[2\] 方法三是双层for循环遍历所有可能,然后再对比是否反向和正向是一样的。这个方法的时间复杂度也为O(n³),效率较低。\[3\] 综上所述,方法一是解决LeetCode最长回文问题的最优解法。 #### 引用[.reference_title] - *1* [LeetCode_5_最长回文](https://blog.csdn.net/qq_38975553/article/details/109222153)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Leetcode-最长回文](https://blog.csdn.net/duffon_ze/article/details/86691293)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [LeetCode 第5题:最长回文(Python3解法)](https://blog.csdn.net/weixin_43490422/article/details/126479629)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值