【LeetCode】

LC第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.

For example, given:
s: "barfoothefoobarman"
words: ["foo", "bar"]

You should return the indices: [0,9].
(order does not matter).

这道题纠结了大半天,主要是题目要求是O(n)的解法,最容易想到的是下面这种暴力搜索的方法,会超时:

 

 1 public static List<Integer> findSubstring(String S, String[] L) {
 2     List<Integer> res = new ArrayList<Integer>();
 3     if (S == null || L == null || L.length == 0) return res;
 4     int len = L[0].length(); // length of each word
 5     
 6     Map<String, Integer> map = new HashMap<String, Integer>(); // map for L
 7     for (String w : L) map.put(w, map.containsKey(w) ? map.get(w) + 1 : 1);
 8     
 9     for (int i = 0; i <= S.length() - len * L.length; i++) {
10         Map<String, Integer> copy = new HashMap<String, Integer>(map);
11         for (int j = 0; j < L.length; j++) { // checkc if match
12             String str = S.substring(i + j*len, i + j*len + len); // next word
13             if (copy.containsKey(str)) { // is in remaining words
14                 int count = copy.get(str);
15                 if (count == 1) copy.remove(str);
16                 else copy.put(str, count - 1);
17                 if (copy.isEmpty()) { // matches
18                     res.add(i);
19                     break;
20                 }
21             } else break; // not in L
22         }
23     }
24     return res;
25 }

然后发现了大家都会使用的所谓的移动窗口来匹配字符串的方法,这样可以保证复杂度为O(n)。

基本思路是维护一个窗口,如果当前单词在哈希中,则继续移动窗口右端,否则窗口左端可以跳到字符串下一个单词了。

而且这里的字符串数组的字符串长度是相同的,所以在写两层循环的时候注意循环停止条件。

 1 class Solution {
 2     public List<Integer> findSubstring(String s, String[] words) {
 3         List<Integer> res = new ArrayList<>();
 4         if(words == null || words.length == 0 || s == null || s.length() == 0)
 5             return res;
 6         int n = s.length(), wcl = words.length;
 7         HashMap<String, Integer> map = new HashMap<>();
 8         for(String word : words)
 9             map.put(word, map.containsKey(word) ? map.get(word) + 1 : 1);
10         int wl = words[0].length();
11         for(int i = 0; i < wl; i++){//注意循环终止条件
12             int left = i, count = 0;
13             HashMap<String, Integer> tmp = new HashMap<>(); //辅助哈希表
14             for(int j = i; j <= n - wl; j += wl){
15                 String str = s.substring(j, j + wl);
16                 if(map.containsKey(str)){
17                     if(tmp.containsKey(str))
18                         tmp.put(str, tmp.get(str)+1);
19                     else
20                         tmp.put(str, 1);
21                     if(tmp.get(str) <= map.get(str))
22                         count++;
23                     else{                    //add more word
24                         while(tmp.get(str) > map.get(str)){
25                             String str1 = s.substring(left, left + wl);
26                             if(tmp.containsKey(str1)){
27                                 tmp.put(str1, tmp.get(str1)-1);
28                                 if(tmp.get(str1) < map.get(str1))// 只有在小于的时候count--,等于的时候相当于用后面的词替换了前面相同的词,count不变
29                                     count--;
30                             }
31                             left += wl;
32                         }
33                     }
34                     if(count == wcl){
35                         res.add(left);
36                         String str2 = s.substring(left, left + wl);  
37                         if(tmp.containsKey(str2))  
38                             tmp.put(str2, tmp.get(str2)-1);  
39                         count--;  
40                         left += wl; 
41                     }
42                 }
43                 else{
44                     tmp.clear();
45                     count = 0;
46                     left = j + wl;
47                 }
48             }
49         }
50         return res;
51     }
52 }

一道很类似的题目:76. 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.

模仿上面一道题用两个哈希的写法,结果跑出了倒数10%的成绩。。。

 1 class Solution {
 2     //模仿的Substring with Concatenation of All Words中的解法,用了两个哈希,结果跑了 66 ms。。。
 3     //别人都是用的数组,最快的只有3、4ms。。。
 4     public String minWindow(String s, String t) {
 5         String res = "";
 6         if(s.length() == 0 || t.length() == 0 || s.length() < t.length())
 7             return res;
 8         HashMap<Character, Integer> map = new HashMap<>();
 9         HashMap<Character, Integer> window = new HashMap<>();
10         for (int i = 0; i < t.length(); i++){
11             char c = t.charAt(i);
12             map.put(c, map.containsKey(c) ? map.get(c) + 1 : 1);
13         }
14         int minLen = Integer.MAX_VALUE, count = 0;
15         for(int slow = 0, fast = 0; fast < s.length(); fast++){
16             char c = s.charAt(fast);
17             if(map.containsKey(c)){
18                 if(window.containsKey(c))
19                     window.put(c, window.get(c) + 1);
20                 else
21                     window.put(c, 1);
22                 if(window.get(c) <= map.get(c))
23                     count++;
24             }
25             if(count == t.length()){
26                 char cc = s.charAt(slow);
27                 while(!map.containsKey(cc) || window.get(cc) > map.get(cc)){
28                     if(window.containsKey(cc))
29                         window.put(cc, window.get(cc) - 1);
30                     cc = s.charAt(++slow);
31                 }
32                 if(fast - slow + 1 < minLen){
33                     minLen = fast - slow + 1;
34                     res = s.substring(slow, slow + minLen);
35                 }
36             }
37         }
38         return res;
39     }
40 }

事实证明,这个地方用数组就可以解决问题,思想还是一样的移动窗口的思想,事实上,其实这个是子串或者字符串匹配很常用的思想,可以把复杂度降低到O(n)。

别人跑了4ms的代码:

 1 class Solution {
 2     public String minWindow(String s, String t) {
 3         if (s == "" || t.length() > s.length()) {
 4             return "";
 5         }
 6         int[] map = new int[128];
 7         int start = 0, end = 0, count = t.length(), minStart = 0, minLen = Integer.MAX_VALUE;
 8         for (char ch : t.toCharArray()) {
 9             map[ch]++;
10         }
11         while (end < s.length()) {
12             if (map[s.charAt(end)] > 0) {
13                 count--;
14             }
15             map[s.charAt(end)]--;
16             end++;
17             while (count == 0) {
18                 if (minLen > end - start) {
19                     minLen = end - start;
20                     minStart = start;
21                 }
22                 map[s.charAt(start)]++;
23                 if (map[s.charAt(start)] > 0) {
24                     count++;
25                 }
26                 start++;
27             }
28         }
29         return minLen == Integer.MAX_VALUE ? "" : s.substring(minStart, minStart + minLen);
30     }
31 }

 

posted on 2017-09-01 21:04  MicN  阅读(189)  评论(0编辑  收藏  举报