题目描述:
求的一个最长的子字符串的长度,该子字符串中每个字符出现的次数都最少为 k。
示例 :
示例 1:
输入:s = "aaabb", k = 3
输出:3
解释:最长子串为 "aaa" ,其中 'a' 重复了 3 次。
示例 2:
输入:s = "ababbc", k = 2
输出:5
解释:最长子串为 "ababb" ,其中 'a' 重复了 2 次, 'b' 重复了 3 次。
使用递归的思路:
一说到递归小伙伴们总会觉得非常的绕,其实这道题使用递归更简单,那正好我们借助这一题来体会下递归。
重点:我们要时刻记住函数是做什么的,我们就把函数理解成一个黑箱,确定输入什么(形参代表什么意思),输出什么(函数调用之后会出现什么结果),不需要关心里面是怎么具体实现的,一看到函数调用就只想输入什么输出什么。
如果还是不太理解的话,就想像一个场景,有一个原始人和一个自动自动售货机,有一天这个原始人得到了很多硬币,而且他发现只要往投币口放入硬币就会得到喝的。我们不妨就把自己想象的笨一点,想象自己是原始人,我们只需要知道,往投币口投入硬币,就可以得到喝的,而不需要关心硬币投入期间和喝的出来期间自动售货机是怎么工作的。
具体到这个题目的话,longestSubstring(s, k) 就是我们的自动售货机,我们需要牢牢记住,这个函数的含义就是——返回字符串(s)满足某个条件的子串的最大长度,这个条件是字符串中每个字符至少出现k次
递归出口条件:
也就是一个字符串在什么情况下就不需要再求最大子串长度了呢?
case1:子串的长度小于k,这样的话无论如何也不会找到满足要求的子串了
case2:字符串的所有字符出现的次数都大于k,这样该字符串本身就是满足要求的子串了。
递归调用:
我们必须明白如果某字符(假设是'c')在字符串中出现的次数小于k,那么该字符串所有包含'字符‘c'的子串都不满足条件,我们就必须把所有不包含字符‘c’的子串拿出来再次判断这些子串满足条件的最长子串的长度,好小伙伴们到这里停一下,还记得我上面说的要记住longestSubstring(s, k)函数的含义吗,这里就用的到啦,没错这个函数就是求满足条件的子串的最大长度的,我们将每个子串作为形参依次放入函数进行调用,形成递归,最后求其中最大的,就是最终结果了
好现在思路我们有了,可是我们怎么能够方便的知道到一个字符串中每个字符串出现的次数呢?
这里我们需要用到Java中的一个Map集合,Map集合存放的是键值对,我们正好可以让出现过的字符作为键,其出现次数为值。这样我们就可以很方便的获得每个字符和其出现次数了。
好我们来看代码 :
String s = "ababbc";
HashMap<Character, Integer> counter = new HashMap();
for (int i = 0; i < s.length(); i++) {counter.put(s.charAt(i), counter.getOrDefault(s.charAt(i), 0) + 1);
}
这里用到的是Java中的HashMap集合,用到了HashMap的一个函数getOrDefault函数在我的这篇博客的HashMap中有详细解释大家可以去看哦,如果现在不想去看的话就理解其为获得每个字符的出现次数就好了它们里面的键值对情况如下:
{a=2, b=3, c=1}
好接下来我们一步一步来写代码
1.首先大家要牢记这个函数的含义,返回满足条件的最长子串的长度
public static int longestSubstring(String s, int k) {}
2.大家尝试写出两个递归出口,并且将HashMap创建出来并写出每个字符出现次数
public static int longestSubstring(String s, int k) {//最长的子字符串的长度,该子字符串中每个字符出现的次数都最少为 kif (s.length() < k) return 0;//递归出口子串的长度小于kHashMap<Character, Integer> counter = new HashMap();for(int i = 0; i < s.length(); i++) {counter.put(s.charAt(i), counter.getOrDefault(s.charAt(i), 0) + 1);}for(){一个循环确保如果字符串s能通过这个循环那么,它必须满足字符串的所有字符出现的次数都大于k return; }return s.length();
3.循环里面些什么?首先肯定要有一个return,让不满足条件的字符串首先结束函数调用,无法出循环体,所以for循环需要遍历到所有字符的数量,来确定它们是否大于等于k。
for (char c : counter.keySet()) {//遍历到所有字符if(counter.get(c)<k){return; }
}
4.如果字符c的数量小于k,那么就需要将字符串以c为分隔符进行切片,求出所有子串中满足条件的最大子串长度。
所以if语句应该这么写
if (counter.get(c) < k) {//含有c的子串不满足条件int res = 0;for (String t : s.split(c+"")) {//便利分片后的子串res = Math.max(res, longestSubstring(t, k));//返回满足条件的最大值}return res;
}
总之,经过对问题思路的分析,再一步步写出代码的过程,递归不是我们的目的,我们可以将一个大的工程拆成可以用同样步骤解决的小工程,直到最后我们可以拆解到一眼就可以看出结果的程度,这个过程中我们调用到了函数本身,这种现象做递归。递归函数到底怎么一层层调用工作的,最好不要仔细去想越想越乱,这是电脑擅长的工作。我们只需要聚焦于函数的输入和输出就好了,把更多的注意力放在拆解子问题、递归终止条件、递归函数的正确性上来。