给你一个下标从 0 开始的数组 words
,它包含 n
个字符串。
定义 连接 操作 join(x, y)
表示将字符串 x
和 y
连在一起,得到 xy
。如果 x
的最后一个字符与 y
的第一个字符相等,连接后两个字符中的一个会被 删除 。
比方说 join("ab", "ba") = "aba"
, join("ab", "cde") = "abcde"
。
你需要执行 n - 1
次 连接 操作。令 str0 = words[0]
,从 i = 1
直到 i = n - 1
,对于第 i
个操作,你可以执行以下操作之一:
- 令
stri = join(stri - 1, words[i])
- 令
stri = join(words[i], stri - 1)
你的任务是使 strn - 1
的长度 最小 。
请你返回一个整数,表示 strn - 1
的最小长度。
示例 1:
输入:words = ["aa","ab","bc"] 输出:4 解释:这个例子中,我们按以下顺序执行连接操作,得到str2
的最小长度:str0 = "aa"
str1 = join(str0, "ab") = "aab"
str2 = join(str1, "bc") = "aabc"
str2
的最小长度为 4 。
示例 2:
输入:words = ["ab","b"] 输出:2 解释:这个例子中,str0 = "ab",可以得到两个不同的 str1: join(str0, "b") = "ab" 或者 join("b", str0) = "bab" 。 第一个字符串 "ab" 的长度最短,所以答案为 2 。
示例 3:
输入:words = ["aaa","c","aba"] 输出:6 解释:这个例子中,我们按以下顺序执行连接操作,得到str2 的最小长度:
str0 = "
aaa"str1 = join(str0, "c") = "aaac"
str2 = join("aba", str1) = "abaaac"
str2
的最小长度为 6 。
提示:
1 <= words.length <= 1000
1 <= words[i].length <= 50
words[i]
中只包含小写英文字母。
方法一:记忆化搜索
我们注意到,字符串连接时,字符串的第一个字符和最后一个字符会影响到连接后字符串的长度。因此,我们设计一个函数
函数
- 如果
$i = n$ ,说明所有字符串已经连接完毕,返回$0$ ; - 否则,我们考虑将第
$i$ 个字符串连接到已经连接的字符串的末尾或者开头,得到连接后字符串的长度$x$ 和$y$ ,则$dfs(i, a, b) = \min(x, y) + |words[i]|$ 。
为了避免重复计算,我们使用记忆化搜索的方法。具体地,我们使用一个三维数组
在主函数中,我们直接返回
时间复杂度
class Solution:
def minimizeConcatenatedLength(self, words: List[str]) -> int:
@cache
def dfs(i: int, a: str, b: str) -> int:
if i >= len(words):
return 0
s = words[i]
x = dfs(i + 1, a, s[-1]) - int(s[0] == b)
y = dfs(i + 1, s[0], b) - int(s[-1] == a)
return len(s) + min(x, y)
return len(words[0]) + dfs(1, words[0][0], words[0][-1])
class Solution {
private Integer[][][] f;
private String[] words;
private int n;
public int minimizeConcatenatedLength(String[] words) {
n = words.length;
this.words = words;
f = new Integer[n][26][26];
return words[0].length()
+ dfs(1, words[0].charAt(0) - 'a', words[0].charAt(words[0].length() - 1) - 'a');
}
private int dfs(int i, int a, int b) {
if (i >= n) {
return 0;
}
if (f[i][a][b] != null) {
return f[i][a][b];
}
String s = words[i];
int m = s.length();
int x = dfs(i + 1, a, s.charAt(m - 1) - 'a') - (s.charAt(0) - 'a' == b ? 1 : 0);
int y = dfs(i + 1, s.charAt(0) - 'a', b) - (s.charAt(m - 1) - 'a' == a ? 1 : 0);
return f[i][a][b] = m + Math.min(x, y);
}
}
class Solution {
public:
int minimizeConcatenatedLength(vector<string>& words) {
int n = words.size();
int f[n][26][26];
memset(f, 0, sizeof(f));
function<int(int, int, int)> dfs = [&](int i, int a, int b) {
if (i >= n) {
return 0;
}
if (f[i][a][b]) {
return f[i][a][b];
}
auto s = words[i];
int m = s.size();
int x = dfs(i + 1, a, s[m - 1] - 'a') - (s[0] - 'a' == b);
int y = dfs(i + 1, s[0] - 'a', b) - (s[m - 1] - 'a' == a);
return f[i][a][b] = m + min(x, y);
};
return words[0].size() + dfs(1, words[0].front() - 'a', words[0].back() - 'a');
}
};
func minimizeConcatenatedLength(words []string) int {
n := len(words)
f := make([][26][26]int, n)
var dfs func(i, a, b int) int
dfs = func(i, a, b int) int {
if i >= n {
return 0
}
if f[i][a][b] > 0 {
return f[i][a][b]
}
s := words[i]
m := len(s)
x := dfs(i+1, a, int(s[m-1]-'a'))
y := dfs(i+1, int(s[0]-'a'), b)
if int(s[0]-'a') == b {
x--
}
if int(s[m-1]-'a') == a {
y--
}
f[i][a][b] = m + min(x, y)
return f[i][a][b]
}
return len(words[0]) + dfs(1, int(words[0][0]-'a'), int(words[0][len(words[0])-1]-'a'))
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
function minimizeConcatenatedLength(words: string[]): number {
const n = words.length;
const f: number[][][] = Array(n)
.fill(0)
.map(() =>
Array(26)
.fill(0)
.map(() => Array(26).fill(0)),
);
const dfs = (i: number, a: number, b: number): number => {
if (i >= n) {
return 0;
}
if (f[i][a][b] > 0) {
return f[i][a][b];
}
const s = words[i];
const m = s.length;
const x =
dfs(i + 1, a, s[m - 1].charCodeAt(0) - 97) -
(s[0].charCodeAt(0) - 97 === b ? 1 : 0);
const y =
dfs(i + 1, s[0].charCodeAt(0) - 97, b) -
(s[m - 1].charCodeAt(0) - 97 === a ? 1 : 0);
return (f[i][a][b] = Math.min(x + m, y + m));
};
return (
words[0].length +
dfs(
1,
words[0][0].charCodeAt(0) - 97,
words[0][words[0].length - 1].charCodeAt(0) - 97,
)
);
}