Time: O(2^n), where n is words.length. Memory n.
class Solution {
public:
int rec(int index, string firstAndLast, vector<string>& words, unordered_map<string,int>& negativesPerIndexAndFirstAndLast)
{
string keyString = to_string(index);
// first and last is just the first and last letters of the word.
keyString.append(firstAndLast);
if (index == words.size()-1)
{
return 0;
}
if (negativesPerIndexAndFirstAndLast.find(keyString) != negativesPerIndexAndFirstAndLast.end())
{
return negativesPerIndexAndFirstAndLast[keyString];
}
else
{
int firstAndLastSize = firstAndLast.size();
// wordAtIndexPlusOne will be prepended or appended to firstAndLast.
string wordAtIndexPlusOne = words[index+1];
int wordAtIndexPlusOneSize = wordAtIndexPlusOne.size();
// Case 0: Prepend wordAtIndexPlusOne
// When I join firstLast with wordAtIndexPlusOne, I may increase the number of negatives (negatives are the number of letter deletions).
// negativesPerPrepend is the number of deletions that happen if I prepend wordAtIndexPlusOne.
// deletions happen when the last letter from the first word matches the first letter from the second word.
int negativesPerPrepend = (firstAndLast[0] == wordAtIndexPlusOne[wordAtIndexPlusOneSize-1]) ? 1 : 0;
// Join firstAndLast with wordAtIndexPlusOne, but just keep the first and last letters.
string newWord = "";
newWord.append(1, wordAtIndexPlusOne[0]);
newWord.append(1, firstAndLast[firstAndLastSize-1]);
int resultFromPrepend = rec(index+1, newWord, words, negativesPerIndexAndFirstAndLast) + negativesPerPrepend;
// Case 1: Append wordAtIndexPlusOne Case
newWord = "";
// negativesPerAppend is the number of deletions if I append wordAtIndexPlusOne.
int negativesPerAppend = (firstAndLast[firstAndLastSize-1] == wordAtIndexPlusOne[0]) ? 1 : 0;
newWord.append(1, firstAndLast[0]);
newWord.append(1, wordAtIndexPlusOne[wordAtIndexPlusOneSize-1]);
int resultAppend = rec(index+1, newWord, words, negativesPerIndexAndFirstAndLast) + negativesPerAppend;
int maxNegatives = max(resultFromPrepend, resultAppend);
negativesPerIndexAndFirstAndLast.insert({keyString, maxNegatives});
return negativesPerIndexAndFirstAndLast[keyString];
}
}
int minimizeConcatenatedLength(vector<string>& words) {
// Sum of all the word lengths in words
int sum = 0;
for (const string& word : words)
{
sum += word.size();
}
// The number of deletions per "index and first and last letters".
// So at index 0, the first word is "abc", the "index plus first and last letters" is "0ac"
unordered_map<string,int> negativesPerIndexAndFirstAndLast{};
// Don't need the whole first word. Shorten word to just the first and last letters of the word.
string firstWord;
firstWord.append(1, words[0][0]);
if (words[0].size() > 1)
{
firstWord.append(1, words[0][words[0].size()-1]);
}
// Trying all the cases, this is the max number of deletions. I'm calling them negatives.
int bestNegatives = rec(0, firstWord, words, negativesPerIndexAndFirstAndLast);
// Subtract the number of deleted letters from the sum.
return sum - bestNegatives;
}
};