题目要求从给定的字符串数组 words
和对应的 groups
数组中,找到一个最长的子序列,满足以下条件:1) 相邻下标的 groups
值不同;2) 相邻字符串长度相等且汉明距离为1。通过动态规划的方法,可以解决该问题。具体步骤包括:初始化 dp
数组记录子序列长度,prev
数组记录前驱节点,遍历所有可能的子序列组合,更新 dp
和 prev
数组,最后根据 prev
数组回溯得到最长子序列。时间复杂度为 O(n² * L),空间复杂度为 O(n + nL),其中 n 是数组长度,L 是字符串的平均长度。
目录
题目链接:2901. 最长相邻不相等子序列 II - 力扣(LeetCode)
题目链接:2901. 最长相邻不相等子序列 II - 力扣(LeetCode)
注:下述题目描述和示例均来自力扣
题目描述
给你一个整数 n
和一个下标从 0 开始的字符串数组 words
,和一个下标从 0 开始的数组 groups
,两个数组长度都是 n
。
两个长度相等字符串的 汉明距离 定义为对应位置字符 不同 的数目。
你需要从下标 [0, 1, ..., n - 1]
中选出一个 最长子序列 ,将这个子序列记作长度为 k
的 [i0, i1, ..., ik - 1]
,它需要满足以下条件:
- 相邻 下标对应的
groups
值 不同。即,对于所有满足0 < j + 1 < k
的j
都有groups[ij] != groups[ij + 1]
。 - 对于所有
0 < j + 1 < k
的下标j
,都满足words[ij]
和words[ij + 1]
的长度 相等 ,且两个字符串之间的 汉明距离 为1
。
请你返回一个字符串数组,它是下标子序列 依次 对应 words
数组中的字符串连接形成的字符串数组。如果有多个答案,返回任意一个。
子序列 指的是从原数组中删掉一些(也可能一个也不删掉)元素,剩余元素不改变相对位置得到的新的数组。
注意:words
中的字符串长度可能 不相等 。
示例 1:
输入:n = 3, words = ["bab","dab","cab"], groups = [1,2,2] 输出:["bab","cab"] 解释:一个可行的子序列是 [0,2] 。 - groups[0] != groups[2] - words[0].length == words[2].length 且它们之间的汉明距离为 1 。 所以一个可行的答案是 [words[0],words[2]] = ["bab","cab"] 。 另一个可行的子序列是 [0,1] 。 - groups[0] != groups[1] - words[0].length = words[1].length 且它们之间的汉明距离为 1 。 所以另一个可行的答案是 [words[0],words[1]] = ["bab","dab"] 。 符合题意的最长子序列的长度为 2 。
示例 2:
输入:n = 4, words = ["a","b","c","d"], groups = [1,2,3,4] 输出:["a","b","c","d"] 解释:我们选择子序列 [0,1,2,3] 。 它同时满足两个条件。 所以答案为 [words[0],words[1],words[2],words[3]] = ["a","b","c","d"] 。 它是所有下标子序列里最长且满足所有条件的。 所以它是唯一的答案。
提示:
1 <= n == words.length == groups.length <= 1000
1 <= words[i].length <= 10
1 <= groups[i] <= n
words
中的字符串 互不相同 。words[i]
只包含小写英文字母。
解法一:
1:理解问题要求
我们需要找到最长的子序列,满足:
- 相邻元素的组别不同。
- 相邻元素的字符串长度相等且汉明距离为1。
2:确定动态规划状态
- 定义
dp[i]
:表示以第i
个元素结尾的最长子序列长度。 - 定义
prev[i]
:记录子序列中第i
个元素的前一个元素下标,用于回溯路径。
3:状态转移方程
对于每个元素i
,遍历所有前面的元素j
(j < i
),检查:
- 组别不同:
groups[i] != groups[j]
。 - 字符串长度相等:
words[i].length == words[j].length
。 - 汉明距离为1:两字符串对应位置恰好有一个字符不同。
若满足条件,则更新:
如果 dp[j] + 1 > dp[i],则:
dp[i] = dp[j] + 1
prev[i] = j
4:初始化
dp
数组初始化为1(每个元素自身构成长度为1的子序列)。prev
数组初始化为-1(无前驱节点)。
5:遍历更新
双重循环遍历所有元素对,更新dp
和prev
数组,同时记录最长子序列的结束位置max_idx
。
6:路径回溯
从max_idx
开始,通过prev
数组逆序回溯路径,得到子序列的索引列表,反转后得到正确顺序。
7:生成结果
根据路径索引,从原数组中取出对应字符串,生成最终结果。
Java写法:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
class Solution {
public List<String> getWordsInLongestSubsequence(String[] words, int[] groups) {
int n = words.length;
int[] dp = new int[n];
int[] prev = new int[n];
Arrays.fill(dp, 1);
Arrays.fill(prev, -1);
int maxLength = 1;
int maxIndex = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < i; j++) {
if (groups[i] != groups[j] && words[i].length() == words[j].length() && hammingDistance(words[i], words[j])) {
if (dp[j] + 1 > dp[i]) {
dp[i] = dp[j] + 1;
prev[i] = j;
}
}
}
if (dp[i] >= maxLength) {
maxLength = dp[i];
maxIndex = i;
}
}
List<Integer> pathIndices = new ArrayList<>();
int current = maxIndex;
while (current != -1) {
pathIndices.add(current);
current = prev[current];
}
Collections.reverse(pathIndices);
List<String> result = new ArrayList<>();
for (int idx : pathIndices) {
result.add(words[idx]);
}
return result;
}
private boolean hammingDistance(String a, String b) {
if (a.length() != b.length()) {
return false;
}
int diff = 0;
for (int i = 0; i < a.length(); i++) {
if (a.charAt(i) != b.charAt(i)) {
diff++;
if (diff > 1) {
return false;
}
}
}
return diff == 1;
}
}
C++写法:
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
class Solution {
public:
vector<string> getWordsInLongestSubsequence(vector<string>& words, vector<int>& groups) {
int n = words.size();
vector<int> dp(n, 1);
vector<int> prev(n, -1);
int max_len = 1, max_idx = 0;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < i; ++j) {
if (groups[i] != groups[j] &&
words[i].size() == words[j].size() &&
hammingDistance(words[i], words[j])) {
if (dp[j] + 1 > dp[i]) {
dp[i] = dp[j] + 1;
prev[i] = j;
}
}
}
if (dp[i] > max_len) {
max_len = dp[i];
max_idx = i;
}
}
vector<int> path;
for (int cur = max_idx; cur != -1; cur = prev[cur]) {
path.push_back(cur);
}
reverse(path.begin(), path.end());
vector<string> res;
for (int idx : path) {
res.push_back(words[idx]);
}
return res;
}
private:
bool hammingDistance(const string& a, const string& b) {
if (a.length() != b.length()) return false;
int diff = 0;
for (int i = 0; i < a.length(); ++i) {
if (a[i] != b[i]) {
if (++diff > 1) return false;
}
}
return diff == 1;
}
};
C写法:
#include <stdlib.h>
#include <string.h>
int hammingDistance(char* a, char* b) {
int lenA = strlen(a);
int lenB = strlen(b);
if (lenA != lenB) return 0;
int diff = 0;
for (int i = 0; i < lenA; ++i) {
if (a[i] != b[i]) {
++diff;
if (diff > 1) return 0;
}
}
return diff == 1;
}
char** getWordsInLongestSubsequence(char** words, int wordsSize, int* groups, int groupsSize, int* returnSize) {
int* dp = (int*)malloc(wordsSize * sizeof(int));
int* prev = (int*)malloc(wordsSize * sizeof(int));
for (int i = 0; i < wordsSize; ++i) {
dp[i] = 1;
prev[i] = -1;
}
int max_len = 1, max_idx = 0;
for (int i = 0; i < wordsSize; ++i) {
for (int j = 0; j < i; ++j) {
if (groups[i] != groups[j]
&& strlen(words[i]) == strlen(words[j])
&& hammingDistance(words[i], words[j])) {
if (dp[j] + 1 > dp[i]) {
dp[i] = dp[j] + 1;
prev[i] = j;
}
}
}
if (dp[i] > max_len) {
max_len = dp[i];
max_idx = i;
}
}
// 构建路径
int* path = (int*)malloc(wordsSize * sizeof(int));
int current = max_idx;
int pathSize = 0;
while (current != -1) {
path[pathSize++] = current;
current = prev[current];
}
// 反转路径
for (int i = 0; i < pathSize / 2; ++i) {
int temp = path[i];
path[i] = path[pathSize - 1 - i];
path[pathSize - 1 - i] = temp;
}
// 生成结果
char** result = (char**)malloc(pathSize * sizeof(char*));
for (int i = 0; i < pathSize; ++i) {
result[i] = strdup(words[path[i]]);
}
*returnSize = pathSize;
free(dp);
free(prev);
free(path);
return result;
}
运行时间
时间复杂度和空间复杂度
总时间复杂度:O(n² * L)
总空间复杂度:O(n + nL)
总结
题目要求从给定的字符串数组 words
和对应的 groups
数组中,找到一个最长的子序列,满足以下条件:1) 相邻下标的 groups
值不同;2) 相邻字符串长度相等且汉明距离为1。通过动态规划的方法,可以解决该问题。具体步骤包括:初始化 dp
数组记录子序列长度,prev
数组记录前驱节点,遍历所有可能的子序列组合,更新 dp
和 prev
数组,最后根据 prev
数组回溯得到最长子序列。时间复杂度为 O(n² * L),空间复杂度为 O(n + nL),其中 n 是数组长度,L 是字符串的平均长度。