SWE · Coding

TikTok 最长无重复子串:左边界为什么只能前进?

用最近位置跳过重复字符,证明窗口不变量,处理abba、空串和Unicode字符定义。

题目重点:用最近位置跳过重复字符,证明窗口不变量,处理abba、空串和Unicode字符定义。

题意与假设

这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。

  • 按Python Unicode code point判断字符相同;连续子串,不允许跳过中间字符。
  • 只返回长度,空串返回0;不做大小写或Unicode归一化。
  • 若需要返回字符串,同长度取最早起点可另保存best_start,最终一次切片。

解题路线

  1. 维护窗口[left,right]和每个字符最近一次出现位置last。处理text[right]时,如果这个字符此前出现过,则窗口左端至少要移到last[char]+1,才能删除上次出现。
  2. 更新必须写left=max(left,last[char]+1),因为上次位置可能已经在窗口外。对abba,处理末尾a时last[a]=0,但left已是2,若直接赋1会倒退并把两个b重新包含。
  3. 不变量是每次处理完当前字符,窗口内没有重复,且left是以right结尾的无重复子串的最小合法左端。旧窗口已无重复,只会新增当前字符这一个潜在重复,跳过它上次出现即可。
  4. 最小合法左端对应当前right的最长窗口,遍历所有right并取最大长度便覆盖全局最优。更新长度是right−left+1,随后记录last[char]=right以服务未来字符。
  5. 每个右端只处理一次,左端仅向前跳,不重新扫描窗口。哈希表查询更新平均O(1),因此总体O(n);需要固定ASCII时可换固定数组,但不能在未知字符域中擅自用256长度数组。
  6. 若采用集合版本,则重复时循环逐个移走左字符直到唯一;每个字符最多入集合、离集合各一次,也为线性。最近位置版本一步跳转更简洁,但仍应讲清max如何维护不变量。
  7. 返回具体子串时保存坐标而非每次切片;实时流可以保留索引和所需窗口数据,想在流结束后输出最长字符串还需保存内容或外部引用。Unicode组合音标/emoji连字若按用户感知字符比较,需要先做grapheme分段。

手动走一遍

abba:窗口依次a、ab、b、ba,答案2。pwwkew答案3,可取wke或kew;pwke是子序列而不是连续子串。🙂你🙂好的答案3,空串0。

Python 参考实现

下面的代码按上文假设实现,适合本地运行并追加自己的测试用例。

代码区可横向滚动;使用键盘时,先聚焦代码区再按方向键查看。

def longest_unique_length(text):
    left, best = 0, 0
    last = {}
    for right, char in enumerate(text):
        left = max(left, last.get(char, -1) + 1)
        best = max(best, right - left + 1)
        last[char] = right
    return best

复杂度

平均O(n)时间;last字典O(U)空间,U为输入不同字符数,最坏O(n)。固定有限字符域时可称O(字符域大小),不能在任意Unicode输入下笼统声称O(1)。

易错点与追问

容易失分的边界

  • 重复字符在窗口外仍把left回退。
  • 把连续子串写成去重子序列。
  • 长度漏掉+1。
  • 每次保存答案都复制整段字符串。

继续深挖

  • 最多允许K个不同字符如何修改?
  • 最长子串中每个字符最多出现两次怎样维护?
  • 如何返回全部同长最优窗口?
  • 流式输入返回长度和返回内容分别需要什么存储?
✓已复制微信号!