【Leetcode】395. Longest Substring with At Least K Repeating Characters

题目地址:

https://leetcode.com/problems/longest-substring-with-at-least-k-repeating-characters/

给定一个长 n n n的字符串 s s s,题目保证 s s s里只有英文小写字母,再给定一个数 k k k,求其每个字母出现次数都不低于 k k k次的最长子串的长度。

法1:分治法。先求一下每个字母在 s s s的长 i i i前缀里出现的次数,记为数组 p p p,则 p [ a ] [ i ] p[a][i] p[a][i]指的是 s [ 0 : i − 1 ] s[0:i-1] s[0:i1]中字母 a a a出现的次数。接下来可以用分治法了。考虑区间 [ l , r ] [l,r] [l,r],遍历该区间,如果发现某个字母 s [ j ] s[j] s[j]在该区间里出现次数没有达到 k k k,那么该字母一定不在合法解里,则可以递归求解 [ l , j − 1 ] [l,j-1] [l,j1] [ j + 1 , r ] [j+1,r] [j+1,r]这两个区间里的合法解,两个解大的就是 [ l , r ] [l,r] [l,r]的全局解;当然遇到 l > r l>r l>r的时候就直接返回 0 0 0了;如果遍历完区间并没有发现出现次数没达到 k k k的字母,那说明整个区间就是合法解,则返回 r − l + 1 r-l+1 rl+1。代码如下:

public class Solution {
    public int longestSubstring(String s, int k) {
    	// 求一下出现次数的前缀和数组
        int[][] preCount = new int[26][s.length() + 1];
        for (int i = 0; i < s.length(); i++) {
            for (char ch = 'a'; ch <= 'z'; ch++) {
                preCount[ch - 'a'][i + 1] = preCount[ch - 'a'][i];
            }
            
            preCount[s.charAt(i) - 'a'][i + 1] = preCount[s.charAt(i) - 'a'][i] + 1;
        }
        
        return dfs(s, 0, s.length() - 1, preCount, k);
    }
    
    private int dfs(String s, int l, int r, int[][] preCount, int k) {
    	// 如果区间空了,则返回0
        if (l > r) {
            return 0;
        }
        
        for (int i = l; i <= r; i++) {
            if (preCount[s.charAt(i) - 'a'][r + 1] - preCount[s.charAt(i) - 'a'][l] < k) {
            	// 这里可以优化一下,i是最左边的次数小于k的字符位置,可以继续向右略过次数小于k的字符
                int right = i;
                while (right <= r && preCount[s.charAt(right) - 'a'][r + 1] - preCount[s.charAt(right) - 'a'][l] < k) {
                    right++;
                }
                
                return Math.max(dfs(s, l, i - 1, preCount, k), dfs(s, right, r, preCount, k));
            }
        }
        
        // 走到这一步了,说明整个区间都是合法解,返回区间长度
        return r - l + 1;
    }
}

时间复杂度 O ( n 2 ) O(n^2) O(n2),空间 O ( n ) O(n) O(n)

法2:双指针。为了增加单调性质,我们枚举区间的不同字母个数,这个个数的范围显然是 1 ∼ 26 1\sim 26 126。例如在枚举字母个数是 c c c的时候,当区间不同字母个数等于 c c c的时候,我们看一下不同字母个数是否等于出现次数不少于 k k k的字母个数,如果相等,则能更新答案;如果一旦区间不同字母个数超过 k k k了,就可以右移左端点了。这样就有了单调性。代码如下:

import java.util.Arrays;

public class Solution {
    public int longestSubstring(String s, int k) {
        int res = 0, diffCount = 0, kCount = 0;
        int[] count = new int[26];
        
        // 枚举区间不同字母个数
        for (int c = 1; c <= 26; c++) {
        	// 每次循环开始的时候要重置一下各个变量
            Arrays.fill(count, 0);
            diffCount = kCount = 0;
            
            for (int i = 0, j = 0; i < s.length(); i++) {
                char chi = s.charAt(i);
                count[chi - 'a']++;
                if (count[chi - 'a'] == 1) {
                    diffCount++;
                }
                if (count[chi - 'a'] == k) {
                    kCount++;
                }
                
                // 如果不同字母个数大于c了,则要右移左端点
                if (diffCount > c) {
                    while (diffCount > c) {
                        char chj = s.charAt(j);
                        
                        count[chj - 'a']--;
                        if (count[chj - 'a'] == 0) {
                            diffCount--;
                        }
                        if (count[chj - 'a'] == k - 1) {
                            kCount--;
                        }
                        
                        j++;
                    }
                } else if (diffCount == c) {
                    if (diffCount == kCount) {
                        res = Math.max(res, i - j + 1);
                    }
                }
            }
        }
        
        return res;
    }
}

时间复杂度 O ( n ) O(n) O(n),空间 O ( 1 ) O(1) O(1)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值