LeetCode 30.
Tags: LeetCode
Question
You are given a string s and an array of strings words. All the strings of words are of the same length.
A concatenated string is a string that exactly contains all the strings of any permutation of words concatenated.
For example, if words = ["ab","cd","ef"], then "abcdef", "abefcd", "cdabef", "cdefab", "efabcd", and "efcdab" are all concatenated strings. "acdbef" is not a concatenated string because it is not the concatenation of any permutation of words.
Return an array of the starting indices of all the concatenated substrings in s. You can return the answer in any order.
Example
Example 1:
Input: s = “barfoothefoobarman”, words = [“foo”,”bar”]
Output: [0,9]
Explanation:
The substring starting at 0 is “barfoo”. It is the concatenation of [“bar”,”foo”] which is a permutation of words. The substring starting at 9 is “foobar”. It is the concatenation of [“foo”,”bar”] which is a permutation of words.
Example 2:
Input: s = “wordgoodgoodgoodbestword”, words = [“word”,”good”,”best”,”word”]
Output: []
Explanation:
There is no concatenated substring.
Example 3:
Input: s = “barfoofoobarthefoobarman”, words = [“bar”,”foo”,”the”]
Output: [6,9,12]
Explanation:
The substring starting at 6 is “foobarthe”. It is the concatenation of [“foo”,”bar”,”the”]. The substring starting at 9 is “barthefoo”. It is the concatenation of [“bar”,”the”,”foo”]. The substring starting at 12 is “thefoobar”. It is the concatenation of [“the”,”foo”,”bar”].
Intuition
Even I know this problem can be solved by Sliding Window, it’s complicated.
The following is my trial and yours is probably better than this so just give it a look :D
I first tried delcare an array to record which word in words had been visited and use Queue to record the index.
However, this solution pass over 80% testcases but will be failed on duplicate words.
Then, I tried use HashMap to keep the numbers of each word and use Queue to make sure FIFO.
Approach
Delcare two variables to record temporary data:
-
Queue: keep current consecutive included words in a sequence
-
Map: keep usable number of words
It comes in three sitautions:
- (1) If current portion is not the key in Map, clear the queue and reset the map (reset words we can use)
- (2) If current portion matches the key but the number is not enough, poll out the queue while the usable number added by one until enough
- (3) If current portion matches the key and the number is enough, add portion to the queue and the matched number minus one
Afterwards:
- (RESULT) If Queue size is equal to the length of array words, meaning the substring(multiple portion concatenated) is existed, record the result and poll out the queue once (because portion[1] to portion[n] is still qualified)
Take Exmaple 1 to explain how my solution works:
Input: s = “barfoothefoobarman”, words = [“foo”,”bar”]
- init: i=0, j=0, Queue=[], Map=[“foo”,”bar”]=>[1, 1], RESULT=[]
- i=0, j=3, portion=substring(0, 3)=”bar” => (3) => Queue=[“bar”], Map=[“foo”,”bar”]=>[1, 0]
- i=0, j=6, portion=substring(3, 6)=”foo” => (3) => Queue=[“bar”, “foo”], Map=[“foo”,”bar”]=>[0, 0]
- (RESULT) => Queue=[“foo”], Map=[“foo”,”bar”]=>[0, 1], RESULT=[0]
- i=0, j=9, portion=substring(6, 9)=”the” => (1) => Queue=[], Map=[“foo”,”bar”]=>[1, 1]
- i=0, j=12, portion=substring(9, 12)=”foo” => (3) => Queue=[“foo”], Map=[“foo”,”bar”]=>[0, 1]
- i=0, j=15, portion=substring(12, 15)=”bar” => (3) => Queue=[“foo”, “bar”], Map=[“foo”,”bar”]=>[0, 0]
- (RESULT) => Queue=[“bar”], Map=[“foo”,”bar”]=>[1, 0], RESULT=[0, 9]
- i=0, j=18, portion=substring(15, 18)=”man” => (1) => Queue=[], Map=[“foo”,”bar”]=>[1, 1]
- i=1, …
Complexity
- Time complexity: O(wlen * slen)
wlen: length of array words
slen: length of s
- Space complexity: O(n)
Code
class Solution {
public List<Integer> findSubstring(String s, String[] words) {
int wlen = words[0].length();
Queue<String> queue = new LinkedList<>();
Map<String, Integer> map = new HashMap<String, Integer>();
Map<String, Integer> copy = new HashMap<String, Integer>();
for (String w: words) {
if (map.containsKey(w)) {
map.put(w, map.get(w) + 1);
copy.put(w, copy.get(w) + 1);
}else{
map.put(w, 1);
copy.put(w, 1);
}
}
List<Integer> result = new ArrayList<>();
for (int i=0; i<wlen; i++) { //wlen
for (int j=i; j<=s.length()-wlen; j+=wlen) { //slen / wlen
String portion = s.substring(j, j+wlen);
int index = map.getOrDefault(portion, -1);
if (index < 0) {
queue.clear();
map.clear();
map.putAll(copy);
continue;
}else if (index == 0) { //consumed
while (queue.size() > 0) {
String back = queue.peek();
if (back.equals(portion)) {
queue.poll();
queue.offer(portion);
break;
}else{
map.put(back, map.get(back) + 1);
queue.poll();
}
}
continue;
}
// matched
queue.offer(portion);
map.put(portion, map.get(portion) - 1);
if (queue.size() == words.length) {
String back = queue.poll();
map.put(back, map.get(back) + 1);
result.add(j - queue.size() * wlen);
}
}
queue.clear();
map.clear();
map.putAll(copy);
}
return result;
}
}
Check out the description of this problem at LC 30.