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

這兩道題有一定的聯繫和區別:這類尋找子序列組合的題目大都可以採取雙指針+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));
    }
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章