您的位置:首页 > 其它

[Leetcode]Substring with Concatenation of All Words & Minimum Window Substring

2016-09-03 01:01 495 查看
这两道题有一定的联系和区别:这类寻找子序列组合的题目大都可以采取双指针+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));
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  双指针 HashTable