对一个字符串,找出一个最长的子串长度,这个子串中所有字符出现的次数至少为k。
1.滑动窗口
一开始把题目看成了,子串中每个字符至多出现k次。如果是这样,那么是一道典型的滑动窗口的题目。
然而,题目是至少出现k次。这样一来,滑动窗口不再适用。因为,在字符出现至多k次的问题中,当窗口尾部的字符超过k个,意味着只需要将窗口头部往后移动直至尾部字符等于k个;当尾部字符出现次数小于k次时,只需要将窗口尾部继续往后移动即可。而在字符出现至少k次的问题中,当尾部字符出现次数小于k次时,无法判断是移动窗口头部(舍弃尾部当前字符)还是移动窗口尾部(期待后面还有更多当前尾部字符)。
2.前缀和
然后想到了前缀和计数,然后用n^2的时间复杂度,检查每个子串是否满足所有字符至少出现k次。不出所料的超时。
3.一个想法
先对整个字符串的字符进行计数,假如存在出现次数少于k次的字符,那么目标串一定出现在被这个字符分隔开的几个字符串中。想到这里,我并没有得到什么更多的思路。。。
4.看了眼题解后
往深一步想,假如存在出现次数少于k次的字符,那么目标串一定出现在被这个字符分隔开的几个字符串中。那么,对这里的每一个分割产生的字符串,再求所有字符出现次数大于k的最长子串。不就得到原问题的一个子问题了吗?也就找到了递归关系。
class Solution { public: vector<string> split(string s, char c){ string temp; vector<string> ret; for(decltype(s.size())i=0; i<s.size(); i++){ if(s[i] == c){ if(temp.size()){ ret.push_back(temp); temp = ""; } }else{ temp += s[i]; } } if(temp.size()) ret.push_back(temp); return ret; } int longestSubstring(string s, int k) { vector<int> ctr(26, 0); for(auto c : s) ctr[c-'a']++; int ret = 0, i; for(i=0; i<26; i++) if(ctr[i] > 0 && ctr[i] < k){ auto substrs = split(s, i+'a'); for(auto str : substrs) ret = max(ret, longestSubstring(str, k)); break; } if(i == 26) return s.size(); return ret; } };