题目描述
思路解析
一句话答案:LeetCode 522 最长特殊序列 II:在字符串列表里找最长的、不是其它任何串子序列的那个串——逐个当候选,用双指针判它是否被别人包含,全扛住就用长度刷新答案,时间 O(n²·L)、空间 O(1)。
特殊序列凭什么算特殊,要返回什么
给一个字符串列表 strs,要找出里面最长的「特殊序列」的长度。特殊序列指某个串独有的子序列,也就是它不是其它任何串的子序列。子序列就是删掉原串里若干字符、剩下的按原顺序拼起来,不要求连续。题面例子 strs=["aba","cdc","eae"] 三个串互不为子序列,答案是最长的 3;strs=["aaa","aaa","aa"] 每个串都被别人包含,返回 -1。
为什么不能直接挑最长的串交上去
直觉是把最长的串直接交上去:它最长,总不会是更短串的子序列。可这招碰上重复串就漏。strs=["aaa","aaa","aa"] 里最长的是 "aaa",但另一个 "aaa" 和它一模一样、互为子序列,谁都不独有,长度并列第一也照样淘汰。长度只是候选资格,真正要判的是有没有被别人包含,得挨个比。
怎么判一个串是不是另一个串的子序列
判断「s 是不是 t 的子序列」用双指针,两个下标各扫一个串。i 指 s、j 指 t,都从 0 起。看 t[j]:正好是 s 当前要找的 s[i],这一位就对上,i 挪一格找下一个;对不上,i 不动,只让 j 右移接着找。t 扫完时若 i 已走到 s 末尾(i 等于 s 的长度),说明 s 每个字符都按顺序在 t 里出现过,s 就是 t 的子序列。一个串只要被任意别的串包含就出局,所以两两各判一次即可。
整个流程是怎么把答案挑出来的
主流程两层循环:外层把每个串轮流当候选 s,内层拿它和其余串 t 逐个判子序列,要跳过自己(下标 i≠j,因为任何串都是自身的子序列,不跳过必被自己判掉)。s 被某个 t 包含就没戏,换下一个候选;一路比到底没被任何串包含,它就是特殊序列,用长度更新答案最大值。全部走完,答案初值 -1 要么被刷新过、要么原样保持。
题面两组数据,逐个候选试一遍
先看 strs=["aba","cdc","eae"]。候选 "aba" 比 "cdc":c、d、c 里找不到打头的 a,i 停在 0,不是子序列;再比 "eae",只有中间那个 a 对上一个,走完 i 没到 3,也不是。都没包含它,"aba" 特殊,答案 3;另外两个同理各自独有。
再看 strs=["aaa","aaa","aa"]。第一个 "aaa" 比第二个 "aaa",三个 a 全对上、i 走到 3,被包含淘汰;第二个同样被第一个吃掉;"aa" 比 "aaa" 也全中被包含。三个全出局,答案保持 -1。
跳过自己没写、子序列错当子串,会怎样
内层忘了 i≠j 这道跳过自己的关卡,每个串都会先被自己判成子序列,没有一个能活下来,答案永远卡在 -1。把「子序列」错当成连续「子串」也常见:"aa" 是 "aba" 的子序列(取第 0、2 位两个 a),却不是连续子串,用子串的判法会把本该淘汰的串误放进来。别被「最长」带偏,两个相同的最长串会互相抵消,长度再大也不算数。
复杂度上,n 个串两两配对 O(n²),每对再做一次 O(L) 子序列扫描(L 为串长),合起来 O(n²·L);只用 i、j 两个下标和一个记分变量,空间 O(1)。
▶ 动画逐步走查(文字版)——想跟着上方动画一帧帧对照就展开
记住「逐个串去比,被别人包含就淘汰,扛过所有人就特殊」,下面每一帧都在套它。
轮到第 0 个串 "aba" 当候选。逐个拿别的串去试:只要它是某个串的子序列就被淘汰,全扛住才算特殊。
t 的第 0 位 "c" 不是候选要找的 "a",i 指针不动,j 继续右移找下一个。
t 的第 1 位 "d" 不是候选要找的 "a",i 指针不动,j 继续右移找下一个。
t 的第 2 位 "c" 不是候选要找的 "a",i 指针不动,j 继续右移找下一个。
整个 "cdc" 扫完了,"aba" 没能被它完整包含,"aba" 不是 "cdc" 的子序列。继续拿下一个串来比。
t 的第 0 位 "a" 正好等于候选要找的 "a",命中!候选已匹配 1/3 个字符。
t 的第 1 位 "b" 正好等于候选要找的 "b",命中!候选已匹配 2/3 个字符。
t 的第 2 位 "a" 正好等于候选要找的 "a",命中!候选已匹配 3/3 个字符。"aba" 被 "aba" 完整包含了。
候选 "aba" 被 "aba" 完整包含,它不是独有的,整串标红淘汰。
轮到第 1 个串 "cdc" 当候选。逐个拿别的串去试:只要它是某个串的子序列就被淘汰,全扛住才算特殊。
t 的第 0 位 "a" 不是候选要找的 "c",i 指针不动,j 继续右移找下一个。
t 的第 1 位 "b" 不是候选要找的 "c",i 指针不动,j 继续右移找下一个。
t 的第 2 位 "a" 不是候选要找的 "c",i 指针不动,j 继续右移找下一个。
整个 "aba" 扫完了,"cdc" 没能被它完整包含,"cdc" 不是 "aba" 的子序列。继续拿下一个串来比。
又一个 "aba",和刚才比过的那个完全相同,"cdc" 的第一个字符 "c" 在里面照样找不到,直接跳过。
t 的第 0 位 "a" 不是候选要找的 "c",i 指针不动,j 继续右移找下一个。
t 的第 1 位 "b" 不是候选要找的 "c",i 指针不动,j 继续右移找下一个。
整个 "ab" 扫完了,"cdc" 没被它完整包含。所有对手都比完了,没人包含 "cdc",准备判它是特殊序列。
候选 "cdc" 扛过了所有对手,谁也包不住它,它就是特殊序列!整串标绿,用它的长度 3 刷新答案为 3。
轮到第 2 个串 "aba" 当候选。逐个拿别的串去试:只要它是某个串的子序列就被淘汰,全扛住才算特殊。
t 的第 0 位 "a" 正好等于候选要找的 "a",命中!候选已匹配 1/3 个字符。
t 的第 1 位 "b" 正好等于候选要找的 "b",命中!候选已匹配 2/3 个字符。
t 的第 2 位 "a" 正好等于候选要找的 "a",命中!候选已匹配 3/3 个字符。"aba" 被 "aba" 完整包含了。
候选 "aba" 被 "aba" 完整包含,它不是独有的,整串标红淘汰。
轮到第 3 个串 "ab" 当候选。逐个拿别的串去试:只要它是某个串的子序列就被淘汰,全扛住才算特殊。
t 的第 0 位 "a" 正好等于候选要找的 "a",命中!候选已匹配 1/2 个字符。
t 的第 1 位 "b" 正好等于候选要找的 "b",命中!候选已匹配 2/2 个字符。"ab" 被 "aba" 完整包含了。
候选 "ab" 被 "aba" 完整包含,它不是独有的,整串标红淘汰。
回看全程:两个 "aba" 互为子序列、"ab" 是 "aba" 的子序列,全被淘汰;只有 "cdc" 谁也包不住,它就是最长特殊序列,答案 3。
边界先想清:全相同为 -1、互不包含取最长、短串被长串吃掉。
两个高频追问:和 LC521 的区别、能否排序优化。
参考代码
from __future__ import annotationsfrom typing import *from collections import *from functools import *from itertools import *from math import *from heapq import *from bisect import *class Solution: def findLUSlength(self, strs: List[str]) -> int: def check(s: str, t: str): i = j = 0 while i < len(s) and j < len(t): if s[i] == t[j]: i += 1 j += 1 return i == len(s) ans = -1 for i, s in enumerate(strs): for j, t in enumerate(strs): if i != j and check(s, t): break else: ans = max(ans, len(s)) return ans复杂度
- 时间:O(n² · L),n 个串两两比较,每次子序列判定 O(L)
- 空间:O(1),只用双指针 i、j 和记分变量
易错点
面试追问把动画讲成自己的话
追问这题和「最长特殊序列 I」(LC521)有什么区别?
追问能不能先排序来优化?
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。然后顺着主线继续:
通过删除字母匹配到字典里最长单词
LeetCode 524 · 中等 · 沿着 双指针套路 继续往下推进
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题