至少有K个重复字符的最长子串(分治,递归,滑动窗口)

至少有K个重复字符的最长子串(分治,递归,滑动窗口)

​ 给你一个字符串 s 和一个整数 k ,请你找出 s 中的最长子串, 要求该子串中的每一字符出现次数都不少于 k 。返回这一子串的长度。

解法一、分治

  1. 从题目中找出华点

    ​ 首先,要找的子串是一个连续的子串

    ​ 其次,该子串中的每一字符出现次数都不少于 k

    ​ 那么,串中的所有不满足次数的字符一定不在所求区间内,

    ​ 所以,区间就可以被这样的字符一个个分开。

    ​ 最后,问题区间,被一次次分解,这,就是分治递归.

class Solution {
    
    private int[]  cnt = new int[26];
    private int k ;
    public int longestSubstring(String s, int k) {
        this.k = k;
        if(k==1) return s.length();
        return digui(s,0,s.length()-1);
    }

    public int digui(String s, int i, int j)
    {
        if(i>j||j-i+1<k) return 0;
        int longK = 0,left = i,right = 0;
        Queue<Integer> queue =new LinkedList<>();
        Arrays.fill(cnt, 0);
        //统计区间内每个字符出现的次数 
        for(int z = i; z <= j; ++z){
            cnt[s.charAt(z)-'a']++;
        }
        //队列标记区间内字符不满足条件的索引
        boolean isOk = true;
        for(int z = i; z <= j; ++z){
            if(cnt[s.charAt(z)-'a']<k){
                isOk =false;  			//标记整个区间不是一个满足条件的区间
                queue.offer(z);
            }     
        }
        
        // 字符再分成一个又一个区间递归下去
        while(!queue.isEmpty()){
            right = queue.poll()-1;
            //System.out.println(left+":"+right);
            longK = Math.max(longK, digui(s,left,right));
            left = right+2;
        }
        
        //特殊处理最后一个不满足条件字符的右区间
        if(left!=i)longK = Math.max(longK, digui(s,left,j));
        
        if(isOk) return j-i+1;
        else return longK;
    }
}

时间复杂度: O(ns) , n是字符串长度 ,s是字符集,每次都去除一个字符,递归的深度最大为26, s=26

空间复杂度: O( s 2 s^2 s2)

解法二、滑动窗口(双指针)

  1. 看到这个题,首先想到的就是滑动窗口。

    滑动窗口法,也叫尺取法,可以用来解决一些查找满足一定条件的连续区间的性质(长度等)的问题。由于区间连续,因此当区间发生变化时,可以通过旧有的计算结果对搜索空间进行剪枝,这样便减少了重复计算,降低了时间复杂度。往往类似于“请找到满足xx的最x的区间(子串、子数组)的xx”这类问题都可以使用该方法进行解决。

  2. 本题窗口右边界滑动的时候,无论窗口内区间如何,都无法判断出左边界是否应该右滑,所以就要遍历所有的子串了

  3. 『当确定了窗口内所包含的字符数量时,区间重新具有了二段性质』

  4. 本题的滑动窗口因此可以剪枝了,因为不确定窗口内所包含的字符数量,所以要枚举26个字符的情况。

 class Solution {
    private int[]  cnt = new int[26];
    private int k ;
    
    public int longestSubstring(String s, int k) {
        //滑动窗口枚举26个字符的情况
        this.k = k;
        int longK = 0;
        if(k==1) return s.length();
        for(int i = 1; i <= 26; i++){  
            longK = Math.max(longK, fixedCharInWindow(s, i));
        }
        return longK;
  
    }
    public int fixedCharInWindow(String s, int typeCount)
    {
        Arrays.fill(cnt, 0);
        int p = 0; //目前窗口内字符的种类数量
        int sum = 0; //窗口内达标数量
        int res = 0; //结果
        int left, right;
        for(right = 0, left = 0; right<s.length(); ++right){
            int u = s.charAt(right) - 'a';
            cnt[u]++;
            if(cnt[u] == 1) p++;   //达标种类
            if(cnt[u] == k) sum++;	//达标数量
            while(p>typeCount){ 			//字符种类数量不对,开始剪枝
                u = s.charAt(left++) - 'a'; 
                if(--cnt[u] == 0) p--;
                if(cnt[u]==k-1) sum--;
            }
            if(sum == p ){
                //每个字符数量达标,种类数量达标
                res = Math.max(res, right-left+1);
            }
        }
        return res;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值