哈希算法题目:最长特殊序列 II
题目描述
给定一个字符串数组 strs,你需要找出这个数组中的最长特殊序列的长度。如果最长特殊序列不存在,返回 -1。
这里的特殊序列定义如下:该序列为某个字符串独有的子序列,即不是其他任何字符串的子序列。
字符串的子序列是指这样一个新字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是。
解题过程循序渐进讲解
第一步:理解问题核心
这个问题的关键在于理解“特殊序列”的定义。一个字符串 s 是特殊的,当且仅当它在给定的字符串数组 strs 中,并且它不是数组中任何其他字符串的子序列。
换句话说,我们要在 strs 中找一个字符串,它不能是别的字符串的“子序列”。而它自己当然是自己的子序列,所以这个“别的字符串”不能是它自己。
第二步:初步思路与观察
一个直接的思路是:我们检查数组中的每一个字符串,判断它是否是其他某个字符串的子序列。
- 如果一个字符串
s是另一个不同于它自己的字符串t的子序列,那么s肯定不是特殊序列。 - 如果一个字符串
s不是任何其他字符串的子序列,那么它本身就是一个特殊序列(因为它至少是自己的子序列,并且这个属性是独有的)。
那么,最长的特殊序列,必然出自那些“不是其他字符串子序列”的字符串中。我们只需要找出这些字符串,并取其中最长的那个的长度即可。
为什么这个思路可行?
假设有一个字符串 L 是最长的特殊序列。根据定义,L 一定是 strs 中某个字符串(比如就叫 S)的子序列。但是,L 不能是其他任何字符串的子序列。那么,L 作为 S 的子序列,并且是独有的,这只有在一种情况下成立:L 就是 S 本身。如果 L 是 S 的一个真子序列(即比 S 短),那么 L 也一定是 S 的子序列,这就违反了“独有”的定义(因为 S 自己也包含 L)。因此,最长的特殊序列一定是 strs 中原有的某个完整的字符串。
结论: 我们只需要关注 strs 中那些“不是其他任何字符串的子序列”的字符串,其中最长的那一个的长度就是答案。
第三步:设计算法步骤
基于以上分析,算法可以分解为以下步骤:
- 排序(可选但高效): 将字符串数组按照长度从大到小排序。这样我们可以优先检查长的字符串。一旦我们找到一个满足条件的字符串,它很可能就是最长的,可以避免检查很多短字符串。
- 遍历检查: 按顺序遍历排序后的数组。对于当前字符串
s:
a. 设置一个标志isSubsequence为false。
b. 再次遍历整个数组,对于每一个其他字符串t(即t != s):
- 检查s是否是t的子序列。
- 如果是,则将isSubsequence设为true,并立即停止对t的检查,因为已经证明s不是特殊的了。
c. 如果遍历完所有其他字符串t,发现s不是任何t的子序列(即isSubsequence仍为false),那么恭喜你,s就是一个特殊序列。由于我们是按长度降序遍历的,当前这个s的长度就是最长特殊序列的长度,直接返回s.length()。 - 处理特殊情况: 如果遍历完所有字符串,都没有找到一个是特殊序列,那么返回 -1。
第四步:实现关键函数——判断子序列
我们需要一个辅助函数 isSubsequence(s, t) 来判断字符串 s 是否是字符串 t 的子序列。这是一个经典的双指针问题。
算法如下:
- 初始化两个指针
i和j,分别指向字符串s和t的开头。 - 同时遍历
s和t:- 如果
s[i] == t[j],说明匹配了一个字符,将i和j都向后移动一位。 - 如果
s[i] != t[j],说明当前t的字符不是我们需要的,只将j向后移动一位,继续在t中寻找。
- 如果
- 如果
s的所有字符都被按顺序匹配完了(即i走到了s的末尾),则s是t的子序列,返回true。 - 如果
t先被遍历完,而s还有字符没匹配,则s不是t的子序列,返回false。
举例: 判断 s = "abc" 是否是 t = "ahbgdc" 的子序列。
i=0(‘a’),j=0(‘a’):匹配,i=1,j=1i=1(‘b’),j=1(‘h’):不匹配,j=2i=1(‘b’),j=2(‘b’):匹配,i=2,j=3i=2(‘c’),j=3(‘g’):不匹配,j=4i=2(‘c’),j=4(‘d’):不匹配,j=5i=2(‘c’),j=5(‘c’):匹配,i=3(等于s.length),返回true。
第五步:复杂度分析
- 时间复杂度: 设字符串个数为
n,字符串的平均长度为L。- 排序的时间复杂度为
O(n log n)。 - 最坏情况下,我们需要检查每一个字符串
s。对于每个s,我们需要检查所有其他n-1个字符串t。每次检查(isSubsequence函数)需要O(L)的时间。 - 因此,总的时间复杂度为
O(n log n + n * n * L),即O(n² * L)。
- 排序的时间复杂度为
- 空间复杂度: 主要为排序所占用的空间
O(log n)(如果使用快速排序)或存储排序后数组的空间O(n),这取决于排序算法。除此之外是常数空间。因此空间复杂度为O(n)或O(log n)。
第六步:示例演示
假设 strs = ["aba", "cdc", "eae"]
- 按长度排序(长度都是3,顺序任意,假设不变):
[“aba”, “cdc”, “eae”] - 检查
“aba”:- 检查它是否是
“cdc”的子序列?不是。 - 检查它是否是
“eae”的子序列?不是。 - 所以
“aba”是特殊序列。其长度为 3。
- 检查它是否是
- 因为我们已经按长度降序检查,并且找到了一个长度为3的特殊序列,所以最长特殊序列的长度就是3。返回3。
总结
解决这个问题的关键是认识到“最长特殊序列”必定是数组中的某个原始字符串。然后,我们通过排序优化搜索顺序,再通过双重循环和“判断子序列”的辅助函数,来找出那些“不被其他任何字符串包含”的字符串,并取其中最长者。