题目描述
给你一个整数 n
和一个下标从 0 开始的字符串数组 words
,和一个下标从 0 开始的 二进制 数组 groups
,两个数组长度都是 n
。
你需要从下标 [0, 1, ..., n - 1]
中选出一个 最长子序列,将这个子序列记作长度为 k
的 [i_0, i_1, ..., i_{k - 1}]
,对于所有满足 0 < j + 1 < k
的 j
都有 groups[i_j] != groups[i_{j + 1}]
。
请你返回一个字符串数组,它是下标子序列 依次 对应 words
数组中的字符串连接形成的字符串数组。如果有多个答案,返回任意一个。
子序列 指的是从原数组中删掉一些(也可能一个也不删掉)元素,剩余元素不改变相对位置得到的新的数组。
注意:words
中的字符串长度可能 不相等。
样例
输入:n = 3, words = ["e","a","b"], groups = [0,0,1]
输出:["e","b"]
解释:一个可行的子序列是 [0,2] ,因为 groups[0] != groups[2]。
所以一个可行的答案是 [words[0],words[2]] = ["e","b"]。
另一个可行的子序列是 [1,2],因为 groups[1] != groups[2]。
得到答案为 [words[1],words[2]] = ["a","b"]。
这也是一个可行的答案。
符合题意的最长子序列的长度为 2。
输入:n = 4, words = ["a","b","c","d"], groups = [1,0,1,1]
输出:["a","b","c"]
解释:一个可行的子序列为 [0,1,2] 因为 groups[0] != groups[1] 且 groups[1] != groups[2]。
所以一个可行的答案是 [words[0],words[1],words[2]] = ["a","b","c"]。
另一个可行的子序列为 [0,1,3] 因为 groups[0] != groups[1] 且 groups[1] != groups[3]。
得到答案为 [words[0],words[1],words[3]] = ["a","b","d"]。
这也是一个可行的答案。
符合题意的最长子序列的长度为 3。
限制
1 <= n == words.length == groups.length <= 100
1 <= words[i].length <= 10
0 <= groups[i] < 2
words
中的字符串 互不相同。words[i]
只包含小写英文字母。
算法1
(动态规划) $O(n^2 + nL)$
- 设状态 $f(i)$ 表示以 $i$ 为结尾最长的合法子序列。
- 初始时,$f(i) = 1$。
- 转移时,枚举 $0 \le j < i$,当 $groups(j) \neq groups(i)$ 时,转移 $f(i) = max(f(i), f(j) + 1)$。同时记录 $i$ 转移前序 $g(i)$。
- 最终子序列的最大长度为 $\max(f(i))$,并从最大 $f(i)$ 的 $i$ 开始回溯转移路径,拼接答案。
时间复杂度
- 动态规划的状态为 $O(n)$,转移时间为 $O(n)$。
- 构造答案的时间复杂度为 $O(nL)$。
- 故总时间复杂度为 $O(n^2 + nL)$。
空间复杂度
- 需要 $O(nL)$ 的额外空间存储动态规划的状态和答案。
C++ 代码
class Solution {
public:
vector<string> getWordsInLongestSubsequence(int n, vector<string>& words,
vector<int>& groups
) {
vector<int> f(n, 1), g(n, -1);
int res = 1, p = 0;
for (int i = 1; i < n; i++) {
for (int j = 0; j < i; j++) {
if (groups[j] == groups[i])
continue;
if (f[i] < f[j] + 1) {
f[i] = f[j] + 1;
g[i] = j;
}
}
if (res < f[i]) {
res = f[i];
p = i;
}
}
vector<string> ans;
while (p != -1) {
ans.push_back(words[p]);
p = g[p];
}
reverse(ans.begin(), ans.end());
return ans;
}
};
算法2
(贪心) $O(nL)$
- 可以通过一次遍历贪心的找到最长的合法子序列。记录每个位置的前序位置。
- 如果当前位置和上一个位置相同,则长度不变,这个位置的前序位置为上一个位置的前序位置;如果不同,则长度加 $1$,这个位置的前序位置为上一个位置。
- 最终,一定是从最后一个位置开始往回遍历拼接答案。
时间复杂度
- 遍历 $groups$ 数组一次,遍历字符串数组一次拼接答案,故时间复杂度为 $O(nL)$。
空间复杂度
- 需要 $O(nL)$ 的额外空间存储前序位置和答案。
C++ 代码
class Solution {
public:
vector<string> getWordsInLongestSubsequence(int n, vector<string>& words, vector<int>& groups) {
vector<int> f(n, -1);
for (int i = 1; i < n; i++) {
if (groups[i] != groups[i - 1]) f[i] = i - 1;
else f[i] = f[i - 1];
}
vector<string> ans;
for (int i = n - 1; i != -1; i = f[i])
ans.push_back(words[i]);
reverse(ans.begin(), ans.end());
return ans;
}
};