2901. 最长相邻不相等子序列 II
2901. 最长相邻不相等子序列 II
题目分析
题目要求从 words
数组中选择一个最长的子序列,满足以下条件:
- 相邻元素的
groups
值不同; - 相邻
words
字符串长度相等且汉明距离为 1。
子序列需保持原数组顺序,返回对应的字符串数组。需结合动态规划(DP)和路径记录解决
解题思路
1. 动态规划状态定义
定义 dp[i]
表示以 words[i]
结尾的最长合法子序列长度。
定义 next[i]
记录子序列中 words[i]
的下一个元素下标,用于最终路径重建
2. 状态转移
- 从后往前遍历:
对每个下标i
,检查其后所有j > i
的元素:- 满足下面3个条件
- 1、若
groups[i] != groups[j]
; - 2、且
words[i]
与words[j]
长度相等; - 3、且两者的汉明距离为 1;
- 则更新
dp[i] = max(dp[i], dp[j] + 1)
,并记录next[i] = j
3. 汉明距离计算
- 逐字符比较:
若两字符串长度相同,遍历每个位置统计不同字符数,严格为 1 时合法
4. 路径重建
- 反向回溯:
找到dp
数组中最大值对应的起点maxStart
,沿next
数组依次拼接子序列
代码实现
class Solution:def getWordsInLongestSubsequence(self, words: List[str], groups: List[int]) -> List[str]:def check(s:str, t:str) -> bool:if len(s) != len(t):return Falsediff = 0for a,b in zip(s,t):if a != b:diff += 1if diff > 1:return Falsereturn diff == 1n = len(words)# 动态规划数组,dp[i]表示以 words[i] 结尾的最长子序列长度;dp = [0]*n# 记录路径的数组,next_index[i] 表示在最长子序列中 words[i] 的下一个元素的下标next_index = [0]*n# 记录当前最长子序列的最后一个元素的下标,初始设为最后一个元素(n-1)max_i = n-1# 从倒数第二个元素向前遍历# 确保在处理 i 时,所有 j > i 的 dp[j] 都已经被计算过了for i in range(n-1, -1, -1):# 遍历i之后的所有元素,对于每个 j,检查它是否能接在 i 后面形成合法子序列for j in range(i+1, n):if dp[j] >dp[i] and groups[i] != groups[j] and check(words[i], words[j]):# 状态转移# 如果从 j 转移过来的子序列比当前记录的长度更长# 更新以 i 结尾的子序列的最大长度,+1 是因为我们把 i 这个元素也加入了子序列dp[i] = dp[j]# 记录路径,表示在最优解中 i 后面接的是 jnext_index[i] = j dp[i] += 1# 更新最长子序列起点if dp[i] > dp[max_i]:max_i = ii = max_iresult = ['']*dp[i]for k in range(dp[i]):result[k] = words[i]i = next_index[i]return result
复杂度分析
- 时间复杂度:
- 动态规划部分为 O(n²),汉明距离检查为 O(m)(m 为字符串长度),总时间复杂度 O(n²m)
- 空间复杂度:
- O(n)(存储
dp
和next
数组)
- O(n)(存储
关键点总结
- 动态规划方向:
从后向前遍历,保证每个状态能继承后续最优解 - 条件检查优化:
提前过滤长度不同的字符串,减少无效计算 - 路径记录:
通过next
数组回溯,避免二次遍历
通过上述方法,可以高效解决最长相邻不相等子序列问题,满足题目要求。