這兩道題有一定的聯繫和區別:這類尋找子序列組合的題目大都可以採取雙指針+Map的思想進行處理。
Substring with Concatenation of All Words中,給定了一些等長的單詞words,要求在指定字符串s中找出這些單詞的連續組合序列;
Minimum Window Substring中,給定了一個子串t,要求在指定字符串s中找出子串t中所有字母所構成的最短子序列,可以理解爲原來的單詞更改爲字母了(字母肯定是等長的啊=。=)。
顯然第二道題目放寬了要求,並不需要保證t中所有字母連續按順序出現。
一、題目描述:
==========================================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.
For example, given:
s: "barfoothefoobarman"
words: ["foo", "bar"]
You should return the indices: [0,9]
.
(order does not matter).
================================================Minimum Window Substring===========================================
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).
For example,
S = "ADOBECODEBANC"
T = "ABC"
Minimum window is "BANC"
.
Note:
If there is no such window in S that covers all characters in T, return the empty string ""
.
If there are multiple such windows, you are guaranteed that there will always be only one unique minimum window in S.
二、解決思路:
=====================================Substring with Concatenation of All Words=========================================
題目是給定等長的一組字符串words,要在指定字符串s中找出這些words連續出現的位置,只要滿足他們的任意排列即可,不過一定要保證連續和數量一致。
我們記指定字符串s的長度爲sLen,每個word的長度一致記爲wordLen。我們採用滑動窗口的策略(即雙指針構成一個窗口),窗口的大小windowSize即爲words數組的大小*wordLen,窗口的起始位置記爲startPos。
因爲任何一個滿足要求的字串必然與windowSize大小一致,並且各個word所出現次數必然與words數組中一致,這就是我們對這個子串的要求,有了這兩個要求,就比較清楚思路了。
首先,採用一個HashMap來記錄每個words[i]在words中出現的次數(這裏嚴重吐槽一下Java對map操作之麻煩。。。),記爲map。
然後,採用一個技巧,雖然一般的字符串匹配常常採用KMP、BM等方法,但是這裏由於有了每個單詞的大小wordLen,我們借用這個特點,只需對指定字符串s的起始位置從外層指針i=0進行wordLen次嘗試即可。每次嘗試的時候用一個HashMap記錄窗口中各個單詞出現的次數,記爲appearMap。用一個內層指針j對s字符串取wordLen長度的子串tmp,在原map中查詢其是否出現過:
i. 如果子串tmp在map中出現過,將其在appearMap中出現的次數加一。
如果此時appearMap[tmp]的次數小於map[tmp],說明該子串可以加入當前備選序列;
如果當前appearMap[tmp]的次數大於等於map[tmp],說明子串tmp已重複出現多次,相當於一個錯誤word的出現,但是此時不需要將之前的匹配完成的子串全部移除,而是採用窗口前移的方式,移到當前窗口中tmp出現的第一個位置將這個tmp移出窗口即可。注意此過程中appearMap值的變化!
然後判斷此時所有words是不是都在appearMap出現了且次數與map所要求的相同,如果是,則記錄下此時startPos的值。
ii. 如果子串tmp沒有在map中出現過,說明這是一個完全錯誤的word,此時直接將appearMap清空,窗口起點startPos移動到此時tmp之後的位置。
================================================Minimum Window Substring=========================================
做完前一道題後再做這道,容易慣性的想到也用兩個map去做索引和比較,當然這樣也是可以的,只不過對於未出現的字母不再做錯誤處理。
這裏發現一個比較好的方法:
首先,還像前一道題目一樣用map去記錄字符串t中各個字母的出現次數。
然後,採用兩個指針start和end,分別指向子序列的起始位置和終止位置,初始均爲0,並用一個計數器cnt來計算當前子序列中還需要多少個t中的字母(初始化爲t的長度)。在end到達s末端之前執行如下循環操作:
如果s[end]在map中,將其map[s[end]]減一,如果map[s[end]]已經小於0,說明子序列中已經包含過多的s[end]字母,此時不再對cnt進行減一操作,其他情況需對cnt--。end指針後移一位。
對cnt進行判斷,如果cnt爲0,說明此時s[start,end)序列中已包含所有字母,在此情況下進行以下循環操作:
判斷當前序列長度是否小於之前找到的序列的長度,如果是則更新。
判斷此時s[start]是否是t中的字母,如果是則將map[s[start]]++(因爲接下來要將s[start]移出序列了),如果此時map[s[start]]大於0,說明移出的s[start]在當前子序列中沒有多餘的相同字母,故此時要將cnt++(該操作與之前cnt--操作放在一起理解)。
指針start前移一位,注意!很多人在用前一道題的方法解決這道題的時候不知道如何處理start的前移,感覺要再搜一遍map,這裏可以通過cnt==0的循環條件控制start前移(雖然每次只能前移一位=。=)
循環結束
循環結束
三、源碼:
========================================Substring with Concatenation of All Words=========================================
import java.util.*;
import java.io.*;
public class SubstringWithConcatenation {
public List<Integer> findSubstring(String s, String[] words) {
List<Integer> li = new ArrayList<Integer>();
if (words.length == 0 || s.length() == 0) return li;
int sLen = s.length(), wordLen = words[0].length();
int windowSize = words.length*wordLen;
Map<String, Integer> map = new HashMap<>();
for (int i = 0; i < words.length; i++) {
if (map.containsKey(words[i])) {
map.put(words[i], map.get(words[i])+1);
}
else map.put(words[i], 1);
}
for (int i = 0; i < wordLen; i++) {
int startPos = i, cnt = 0;
Map<String, Integer> appearMap = new HashMap<>();
for (int j = i; j+wordLen <= s.length(); j += wordLen) {
String tmp = s.substring(j, j+wordLen);
if (map.containsKey(tmp)) {
if (appearMap.containsKey(tmp)) {
appearMap.put(tmp, appearMap.get(tmp)+1);
}
else appearMap.put(tmp, 1);
if (appearMap.get(tmp) <= map.get(tmp)) {
cnt++;
}
else {
while (appearMap.get(tmp) > map.get(tmp)) {
String front = s.substring(startPos, startPos+wordLen);
appearMap.put(front, appearMap.get(front)-1);
if (appearMap.get(front) < map.get(front)) {
cnt--;
}
startPos += wordLen;
}
}
if (cnt == words.length) {
li.add(startPos);
String front = s.substring(startPos, startPos+wordLen);
startPos += wordLen;
cnt--;
appearMap.put(front, appearMap.get(front)-1);
}
}
else {
appearMap.clear();
cnt = 0;
startPos = j+wordLen;
}
}
}
return li;
}
public static void main(String[] args) {
SubstringWithConcatenation substringWithConcatenation = new SubstringWithConcatenation();
Scanner scanner = new Scanner(System.in);
String string= scanner.nextLine();
String[] words = new String[Integer.parseInt(args[0])];
for (int i = 0; i < Integer.parseInt(args[0]); i++) {
words[i] = scanner.nextLine();
}
List<Integer> list = substringWithConcatenation.findSubstring(string, words);
for (Integer integer : list) {
System.out.println(integer);
}
}
}
=============================================Minimum Window Substring==================================================
import java.io.*;
import java.util.*;
public class MinimumWindowSubstring {
public String minWindow(String s, String t) {
if (s.length() < t.length()) return "";
Map<Character, Integer> map = new HashMap<>();
for (int i = 0; i < t.length(); i++) {
map.put(t.charAt(i), map.getOrDefault(t.charAt(i), 0)+1);
}
int end=0, start=0, minDist=Integer.MAX_VALUE, cnt=t.length();
String ans = "";
while (end < s.length()) {
char curr = s.charAt(end);
if (map.containsKey(curr)) {
int currValue = map.get(curr);
map.put(curr, currValue-1);
if (currValue > 0) {
cnt--;
}
}
end++;
while (cnt == 0) {
if (end-start < minDist) {
minDist = end-start;
ans = s.substring(start, end);
}
char front = s.charAt(start);
if (map.containsKey(front)) {
int frontValue = map.get(front)+1;
map.put(front, frontValue);
if (frontValue > 0) {
cnt++;
}
}
start++;
}
}
return ans;
}
public static void main(String[] args) {
MinimumWindowSubstring mw = new MinimumWindowSubstring();
Scanner scan = new Scanner(System.in);
String s = scan.nextLine();
String t = scan.nextLine();
System.out.println(mw.minWindow(s, t));
}
}