# Concatenated Words

Published: Sep 14, 2022

Hard Dynamic Programming Depth-First Search String Array

## Problem Description

Given an array of strings `words` (without duplicates), return all the concatenated words in the given list of words.

A concatenated word is defined as a string that is comprised entirely of at least two shorter words in the given array.

Constraints:

• `1 <= words.length <= 10**4`
• `1 <= words[i].length <= 30`
• `words[i]` consists of only lowercase English letters.
• All the strings of words are unique.
• `1 <= sum(words[i].length) <= 10**5`

https://leetcode.com/problems/concatenated-words/

## Examples

``````Example 1:
Input: words = ["cat","cats","catsdogcats","dog","dogcatsdog","hippopotamuses","rat","ratcatdogcat"]
Output: ["catsdogcats","dogcatsdog","ratcatdogcat"]
``````
``````Example 2:
Input: words = ["cat","dog","catdog"]
Output: ["catdog"]
``````

## How to Solve

If it is a string problem and a type of substring comparison, it might be solved by the depth-first search (recursion) changing the length of the word.

An easy approach would be to divide a word to prefix and suffix. Then, check both prefix and suffix are in the given word list. Shift the index and divide the word at a new index, then check.

However, as this problem is categorized in Hard, it is very hard problem. It requires an optimization not to get the time limited exceeded error.

The approach here uses depth-first search (dfs, recursion) and memoization. Also, the current index is passed to dfs function not to create substrings on memory. The memoization is used to check whether the index is already visited or not. During the recursion, only prefix part is looked up to the word set. The suffix part check will be done in the deeper iteration. If thr dfs returns truthy, add the word to the result array.

## Solution

``````class ConcatenatedWords {
private:
bool dfs(unordered_set<string> &word_set, vector<int> &memo, string &word, int idx) {
if (idx == word.size()) { return 1; }
if (memo[idx] != -1) { return memo[idx]; }
bool ret = 0;
for (int i = 1; i <= word.size() - idx - (idx == 0); ++i) {
if (word_set.find(word.substr(idx, i)) != word_set.end()) {
ret = dfs(word_set, memo, word, idx + i);
if (ret) { break; }
}
}
return memo[idx] = ret;
}

public:
unordered_set<string> word_set(words.begin(), words.end());
vector<string> result;
for (string word : words) {
vector<int> memo(word.size(), -1);
if (dfs(word_set, memo, word, 0)) {
result.push_back(word);
}
}
return result;
}
};
``````
``````class ConcatenatedWords {
private int dfs(Set<String> word_set, int[] memo, String word, int idx) {
if (idx == word.length()) { return 1; }
if (memo[idx] != -1) { return memo[idx]; }
int ret = 0;
for (int i = 1; i <= word.length() - idx - (idx == 0 ? 1 : 0); ++i) {
if (word_set.contains(word.substring(idx, idx + i))) {
ret = dfs(word_set, memo, word, idx + i);
if (ret == 1) { break; }
}
}
memo[idx] = ret;
return ret;
}

Set<String> word_set = new HashSet<>();
for (String w : words) { word_set.add(w); }
List<String> result = new ArrayList<>();
for (String word : words) {
int[] memo = new int[word.length()];
for (int i = 0; i < word.length(); ++i) { memo[i] = -1; }
if (dfs(word_set, memo, word, 0) == 1) {
}
}
return result;
}
}
``````
``````/**
* @param {string[]} words
* @return {string[]}
*/
let word_set = new Set(words);

const func = function dfs(memo, word, idx) {
let n = word.length;
if (idx === n) { return 1; }
if (memo[idx] !== -1) { return memo[idx]; }
let ret = 0;
for (let i = 1; i <= n - idx - (idx == 0); i++) {
if (word_set.has(word.substring(idx, idx + i))) {
ret = dfs(memo, word, idx + i);
if (ret) { break; }
}
}
memo[idx] = ret;
return ret;
}

let result = new Array();
for (let word of words) {
let memo = new Array(word.length).fill(-1);
if (func(memo, word, 0)) {
result.push(word);
}
}
return result;
};
``````
``````class ConcatenatedWords:
def findAllConcatenatedWordsInADict(self, words: List[str]) -> List[str]:
word_set = set(words)

def dfs(memo, word, idx):
n = len(word)
if idx == n: return 1
if memo[idx] != -1: return memo[idx]
ret = 0
for i in range(1, n - idx - (idx == 0) + 1):
if word[idx:idx + i] in word_set:
ret = dfs(memo, word, idx + i)
if ret: break
memo[idx] = ret
return ret

result = []
for word in words:
memo = [-1 for _ in range(len(word))]
if dfs(memo, word, 0):
result.append(word)
return result
``````
``````require 'set'

# @param {String[]} words
# @return {String[]}
def find_all_concatenated_words_in_a_dict(words)
word_set = Set.new(words)

dfs = lambda do |memo, word, idx|
n = word.length
if idx == n
return 1
end
if memo[idx] != -1
return memo[idx]
end
ret = 0
(1...(n - idx - (idx == 0 ? 1 : 0) + 1)).each do |i|
if word_set.include?(word[idx...idx + i])
ret = dfs.call(memo, word, idx + i)
if ret == 1
break
end
end
end
memo[idx] = ret
ret
end

result = []
words.each do |word|
memo = Array.new(word.length).fill(-1)
if dfs.call(memo, word, 0) == 1
result << word
end
end
result
end
``````

## Complexities

• Time: `O(n * m)` –> `O(n)` since m: length of the longest word is at most 30.
• Space: `O(m)` – for a recursion stack