[LeetCode]395. Longest Substring with At Least K Repeating Characters滑动窗口+分治(JavaScript)

题目描述

LeetCode原题链接:395. Longest Substring with At Least K Repeating Characters

Given a string s and an integer k, return the length of the longest substring of s such that the frequency of each character in this substring is greater than or equal to k.

 

Example 1:

Input: s = "aaabb", k = 3
Output: 3
Explanation: The longest substring is "aaa", as 'a' is repeated 3 times.

Example 2:

Input: s = "ababbc", k = 2
Output: 5
Explanation: The longest substring is "ababb", as 'a' is repeated 2 times and 'b' is repeated 3 times.

 

Constraints:

  • 1 <= s.length <= 104
  • s consists of only lowercase English letters.
  • 1 <= k <= 105

解法一:分治法(divide and conquer)

根据题目要求可以知道,符合条件的字串的长度至少是k(对应字串中只有一个字母且该字母出现k次的情况);同时,符合条件的字串中所有字母出现的次数都要>=k,换句话说,原字符串中出现次数<k的字母一定不可能出现在满足条件的字串中。那么这些不合法的字母就是分治法中“分”的点。所以,对于原字符串,假设存在这样的分割点pivot,那么我们通过递归不断切分,那么,longestSubstring(s, k) = max(longestSubstring(s.slice(0, pivot), k), longestSubstring(s.slice(pivot + 1), k))。在“治”的过程中,如果当前入参的字符串的长度小于k,直接返回0;如果遍历完整个字符串都找不到上述可以分割的点,那么说明原字符串本身就是符合条件的最长子串,返回s.length。

 1 var longestSubstring = function(s, k) {
 2     if(s.length < k) return 0;
 3     let frequency = new Map();
 4     for(let char of s) {
 5         frequency.set(char, frequency.has(char) ? frequency.get(char) + 1 : 1);
 6     }
 7     for(let pivot = 0; pivot < s.length; pivot++) {
 8         if(frequency.get(s[pivot]) < k) {
 9             let nextPivot = pivot + 1;
10             while(nextPivot < s.length && frequency.get(s[nextPivot]) < k) nextPivot++;
11             return Math.max(longestSubstring(s.slice(0, pivot), k), longestSubstring(s.slice(nextPivot), k));
12         }
13     }
14     return s.length;
15 };

也可以单独用一个util函数来处理迭代:

 1 var longestSubstring = function(s, k) {
 2     return recursion(s, k, 0, s.length);
 3 };
 4 
 5 var recursion = function(s, k, start, end) {
 6     if(end - start < k) return 0;
 7     let frequency = new Map();
 8     for(let i = start; i < end; i++) {
 9         frequency.set(s[i], frequency.has(s[i]) ? frequency.get(s[i]) + 1 : 1);
10     }
11     for(let pivot = start; pivot < end; pivot++) {
12         if(frequency.get(s[pivot]) < k) {
13             let nextPivot = pivot + 1;
14             while(nextPivot < end && frequency.get(s[nextPivot]) < k) nextPivot++;
15             return Math.max(recursion(s, k, start, pivot), recursion(s, k, nextPivot, end));
16         }
17     }
18     return end - start;
19 }

解法二:滑动窗口(sliding window)

分析解法一的时候我们讨论过,符合条件的子串中至少有一个不同的字母。那么至多有多少个不同的字母呢?因为题目说了s中全部都是小写英文字母,英文字母最多有26个,所以至多也不会超过26。还能再缩小一下最大值么?可以。这个最大值和原字符串中不同字母的个数是相同的(即原字符串中所有不同字母的出现次数都>=k),设这个值为maxUniqueChar。于是,uniqueChar的范围就是[1, maxUniqueChar]。我们从1开始遍历这个区间,假设当前遍历的是curUniqueChar,我们用滑动窗口去圈一个子串,要求窗口内的子串有且仅有curUniqueChar个不同的字母,并且这些字母出现的次数都要>=k。过程中用一个maxLen去记录全局最大值。字母的出现次数用hashmap来记录(或者是一个长度为26的数组)

我们知道滑动窗口算法的核心是左右边界start、end的移动条件:窗口内的东西满足条件时右移end扩大窗口,一旦不满足就右移start缩小窗口。对应这道题,如果[start, end]范围内的子串中不同字母的个数unqiue <= curUniqueChar,则右移end扩大窗口,否则右移left缩小窗口。每次扩大或缩小的时候,都要注意新加入或移除的字母对unqiue的影响(如果新加入窗口的字母之前没出现过,unique就要自增1;如果移出的元素不再出现在缩小后的窗口内,unique就要自减1)。此外不要忘记另一个限制——每个不同的字母在窗口中的出现次数。我们用一个计数器kCnt来记录,右移end过程中一旦某一个字母出现次数等于k了,kCnt就自增1;右移start过程中,一旦,如果移出的元素恰好已经出现了k次,说明移出它后该字母在缩小后的窗口中出现的次数就要小于k了,此时需要将kCnt自减1。最后,当unqiue == curUniqueChar && unqiue == kCnt时,说明找到了符合所有条件的子串,更新maxLen。

 1 var longestSubstring = function(s, k) {
 2     let maxLen = 0;
 3     let maxUniqueChar = (new Set(s)).size; // 用set来快速求出最大边界
 4     let frequency = new Array(26);
 5     for(let curUniqueChar = 1; curUniqueChar <= maxUniqueChar; curUniqueChar++) {
 6         let start = 0, end = 0, kCnt = 0, unique = 0, idx = 0;
 7         frequency.fill(0);  // 重置计数数组
 8         while(end < s.length) {
 9             if(unique <= curUniqueChar) {
10                 idx = s[end].charCodeAt() - 'a'.charCodeAt();
11                 if(frequency[idx] == 0) {
12                     unique++;
13                 }
14                 if(++frequency[idx] == k) kCnt++;
15                 end++;
16             }
17             else {
18                 idx = s[start].charCodeAt() - 'a'.charCodeAt();
19                 if(frequency[idx] == k) kCnt--;
20                 if(--frequency[idx] == 0) {
21                     unique--;
22                 }
23                 start++;
24             }
25             if(unique == curUniqueChar && unique == kCnt) {
26                 maxLen = Math.max(maxLen, end - start);
27             }
28         }
29     }
30     return maxLen;
31 };
posted @ 2022-02-14 15:21  夭夭夭夭夭桃子  阅读(72)  评论(0)    收藏  举报