这里聊一聊解一类问题,就是满足某一条件的substring最值问题。
最开始我们以Minimum Window Substring为例,并整理总结leetcode里所有类似题目的通解。
Given a string S and a string T, find the minimum window in S which will contain all the characters in T in complexity O(n).
S = "ADOBECODEBANC" , T = "ABC", Minimum window is "BANC".
我们要把问题拆解成三个字问题:唯一的表示substring, 检查substring是否符合要求,记录最值。
1.
如何唯一的表示一个substring, 有两种方法,一种用头尾指针,一种用尾指针加substring长度。
2.
检查substring是否符合要求,这道题目要求包含T中的每个字母,所以我们需要一个counter来记录T里每个character出现的次数,空间O(26) O(128) O(256) 都行, 可以和面试官讨论。类似题目唯一变化的就是这个部分,可以是每个字符只能出现一次,最多出现两个不同字符,每个字符最多出现K次等等。
3.
记录最值。根据扫描方法的不同,长度有时为r-l,此时r不包含在substring里,有时为r-l+1,此时包含在substring里。
这篇博客主要讲第二个子问题,如何判断substring是否符合要求。以Minimum Window Substring为例。
public class Solution {
public String minWindow(String s, String t) {
// empty check
if(s == null || t == null || s.length() == 0 || t.length() == 0) return "";
// initial counter for valid check
int[] counter = new int[128];
for(char c : t.toCharArray()){
counter[c]++;
}
// variables help represent substring and min/max
char[] chs = s.toCharArray();
// 变量名解释,i表示头即先开始扫描的指针,j表示尾。i,j对counter的操作相反,一个加一个减或者一个减一个加。
// 上面的counter记录每个字符出现次数,count表示substring里满足条件的字符出现总次数(可以从0加到count,也可以从count减少到0.
int j = 0, i=0, minLen = Integer.MAX_VALUE, start = 0, count = t.length();
//这里可以使用for和while两种循环,个人偏向for, 因为i指针要一次走一个字符遍历完整个string.
for( ; i < chs.length; i++){ //逐个扫描
// 我们包含了第i个元素,并且要立即判断是否满足条件。使用--x, 而不是x--.
if(--counter[chs[i]] > -1) {
count--;
}
// 因为我们需要的是最值,中间值我们不在乎,所以一次收敛到最小。
while(count == 0) { // 快速收紧
if(i -j + 1< minLen) {
minLen = i - j + 1;
start = j;
}
// 用 == 0 判断是因为有的substring里个别字符可能偏多,多余的字符不会算作满足条件。下方有简单的例子。
// 一次只移动一个char, 所以这里用 == 0
if(counter[chs[j++]]++ == 0) {
count++;
}
}
}
return minLen == Integer.MAX_VALUE ? "" : new String(chs, start, minLen);
}
}
X表示占位符,不是字母X。(使用空格会被自动消除,所以这里用了X)
S: ADOBECODEBANC T: ABC
i: ADOBECXXXXXXXX
j: XDOBECXXXXXXX
i: XDOBECODEBAXX
j: XXXXXXODEBAXX
注意这一步j最后落在了C后面,而不是B的后面。因为B在T里只出现了一次, i在扫描的时候会减小两次,counter[B] = -1, counter[c] = 0
i: XXXXXXODEBANC
j: XXXXXXXXXBANC
将S,T里字符换成String,变成Leetcode 30, Substring with Concatenation of All Words.
You are given a string, s, and a list of words, words, that are all of the same length. Find all starting indices of substring(s) in s that is a concatenation of each word in words exactly once and without any intervening characters.
s: "barfoothefoobarman", words: ["foo", "bar"], You should return the indices: [0,9]
字符长度为K,我们可能从0, 1, 2, K-1开始组成不同字符, 从K开始会和从O开始重合了。代码如下,不过多解释,可以直接跳过。
public class Solution {
public List<Integer> findSubstring(String s, String[] words) {
List<Integer> res = new ArrayList<Integer>();
int N = s.length(), M = words.length, K = words[0].length();
// check input
if(s == null || K == 0 || N < M*K) return res;
Map<String, Integer> map = new HashMap<>(), curMap = new HashMap<>();
// initial counter
for(String word: words) {
map.put(word, map.getOrDefault(word,0) + 1);
}
// str denote first word, temp denote last word
String str = null, temp = null;
for(int i=0; i< K; i++) {
int count = 0;
// two poniter scan, each time move K steps
for(int l=i, r=i; r+K <= N; r += K) {
str = s.substring(r, r+K);
if(map.containsKey(str)) {
curMap.put(str, curMap.getOrDefault(str,0) + 1);
if(curMap.get(str) <= map.get(str)) count++;
// has a word above the counter of it
while(curMap.get(str) > map.get(str)) {
temp = s.substring(l, l+K);
curMap.put(temp, curMap.get(temp) - 1);
l += K;
if(curMap.get(temp) < map.get(temp)) count--;
}
// should be exactly M, when meet, delete last word
if(count == M){
res.add(l);
temp = s.substring(l, l+K);
curMap.put(temp, curMap.get(temp) - 1);
l += K;
count--;
}
} else {
curMap.clear();
count = 0;
l = r + K;
}
}
curMap.clear();
}
return res;
}
}
下面来三个longest substring.
LC3 Longest Substring Without Repeating Characters
需要查重并且记录上次出现的位置,选择map.
以abcdba为例,i走到b, 用map做检查,发现出现过,把j移到map.get(b)的下一个。
i走到a, 用map做检查,发现出现过,把j移到map.get(a)的下一个。发现不对,因为下一个字符是b已经出现过两次,所以需要一个比较,取map.get(b),map.get(a)的最大值。
public class Solution {
public int lengthOfLongestSubstring(String s) {
// empty check
if(s == null || s.length() == 0) return 0;
// <char, last appear pos>
Map<Character, Integer> map = new HashMap<>();
// scan whole string and calculte
int len = 0;
char[] chs = s.toCharArray();
for(int i=0, j=0; i<chs.length; i++) {
if(map.containsKey(chs[i])) {
j = Math.max(j, map.get(chs[i])+1); // abcdba, need to compare
}
map.put(chs[i], i);
if(i-j+1 > len) {
len = i-j+1;
}
}
return len;
}
}
LC340. Longest Substring with At Most K Distinct Characters
Given a string, find the length of the longest substring T that contains at most 2 distinct characters.
For example, Given s = “eceba”,
T is "ece" which its length is 3.
counter记录每个字符出现次数。 count表示有多少个不同字符,题目要求最多出现k个。
public class Solution {
public int lengthOfLongestSubstringKDistinct(String s, int k) {
// corner case
if(s== null || s.length() == 0) return 0;
// counter, count denote k
int[] counter = new int[128];
// l denote tail, r denote head, two pointer scan
int j = 0, len = 0, count = 0;
for(int i=0; i<s.length(); i++) {
// only denote first appearence
if(counter[s.charAt(i)]++ == 0) {
count++;
}
// at most k, // very important part
if(count > k) {
while( --counter[s.charAt(j++)] > 0);
count--;
}
if(i - j + 1 > len) {
len = i - j +1;
}
}
// return max
return len;
}
}
LC159. Longest Substring with At Most Two Distinct Characters
Given a string, find the length of the longest substring T that contains at most 2 distinct characters.
For example, Given s = “eceba”,
T is "ece" which its length is 3.
是上个题目的简易版,或者特殊版。只有两个字符,我们就不需要一个数组了,只需要两个位置标记就行。p1, p2. 为了让计算最值方便,让p1始终小于p2.
这里需要注意的就是p1,p2在repeating characters出现的时候,同时会指向这个字符串的最后一个。
在判断第三个字符是否出现的时候,首先要比较p1 != p2.
public class Solution {
public int lengthOfLongestSubstringTwoDistinct(String s) {
if(s.isEmpty()) return 0;
int p1 = 0, p2 = 0, max = 1, len = 1;
char[] chs = s.toCharArray();
for(int i=1; i<chs.length; i++) {
if(p1 != p2 && chs[i] != chs[p1] && chs[i] != chs[p2]) {
if(len > max) max = len;
len = i - p1; // always keep p1,p2 ordered p1<=p2
p1 = p2;
p2 = i;
} else {
if(chs[i] == chs[p1]) { // 0 or repeating charcters lead to p1=p2
p1 = p1 == p2 ? i:p2;
}
len++;
p2 = i;
}
}
if (len > max) max = len;
return max;
}
}
今天先写到这里,以后补充。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。