📖 第68课:划分字母区间

3 阅读22分钟

想系统提升编程能力、查看更完整的学习路线,欢迎访问 AI Compass:github.com/tingaicompa… 仓库持续更新刷题题解、Python 基础和 AI 实战内容,适合想高效进阶的你。

📖 第68课:划分字母区间

模块:贪心算法 | 难度:Medium ⭐⭐ LeetCode 链接:leetcode.cn/problems/pa… 前置知识:无 预计学习时间:25分钟


🎯 题目描述

给定一个字符串s,将其划分为尽可能多的片段,使得每个字母最多出现在一个片段中。返回一个列表,表示每个片段的长度。

示例:

输入:s = "ababcbacadefegdehijhklij"
输出:[9,7,8]
解释:
  片段1: "ababcbaca" (a,b,c只在这个片段中)
  片段2: "defegde" (d,e,f,g只在这个片段中)
  片段3: "hijhklij" (h,i,j,k,l只在这个片段中)

约束条件:

  • 1 <= s.length <= 500
  • s仅由小写英文字母组成
  • 划分要尽可能多,即每个片段尽可能短

🧪 边界用例(面试必考)

用例类型输入期望输出考察点
最小输入s="a"[1]单字符情况
无重复s="abcd"[1,1,1,1]每个字符独立成片段
全相同s="aaaa"[4]整个串是一个片段
交错出现s="ababcbaca"[9]a的最后出现位置决定边界
多片段s="eccbbbbdec"[10] → [eccbbbbdec]c出现在首尾,整体一片段

💡 思路引导

生活化比喻

想象你在整理一堆文件,每份文件上标有字母标签(a-z)。你的任务是把文件分成若干堆,要求:每个字母的所有文件必须在同一堆,并且堆数尽可能多(即每堆尽可能小)。

🐌 笨办法:从左往右扫描,遇到一个新字母就开始收集,直到把这个字母的所有文件都放进当前堆。但问题是,收集过程中可能遇到其他字母,它们的"最后一份文件"在更远的地方,你得继续收集...结果越收越多,很难确定何时停止。

🚀 聪明办法:先用一张索引表记录每个字母"最后一份文件"的位置,然后从左往右扫描。当前堆的"终点"就是"当前堆内所有字母的最后位置的最大值"。扫到终点时,这一堆就完成了,开启新一堆。这样每堆都是最短的,堆数最多!

关键洞察

预先记录每个字符最后出现的位置,然后一次扫描,贪心地扩展当前片段的右边界,直到扫描位置追上边界,就切分出一个片段。


🧠 解题思维链

这一节模拟你在面试中"从零开始思考"的过程。

Step 1:理解题目 → 锁定输入输出

  • 输入:s = "ababcbacadefegdehijhklij",26个小写字母组成的字符串
  • 输出:[9, 7, 8],每个片段的长度列表
  • 限制:每个字母只能出现在一个片段中,片段数量要尽可能多(每个片段尽可能短)

Step 2:先想笨办法(暴力法)

从左往右扫描,遇到字符c时,找到c在整个串中最后出现的位置last_c,那么当前片段至少要延伸到last_c。但在扫描到last_c之前,可能遇到其他字符d,它的最后位置last_d可能更远,又得继续扩展...

这需要嵌套循环:

i = 0
while i < len(s):
    end = i
    for j in range(i, end+1):
        last_pos = s.rfind(s[j])  # 每次都要查找最后位置
        end = max(end, last_pos)
    # [i, end]是一个片段
    i = end + 1
  • 时间复杂度:O(n²) — 外层循环n次,内层每次可能扫描O(n)
  • 瓶颈在哪:重复调用rfind查找字符最后位置,每次O(n)

Step 3:瓶颈分析 → 优化方向

核心问题:为什么每次都要重新查找字符的最后位置?字符集只有26个,可以预处理!

优化思路:

  1. 预处理阶段:用哈希表记录每个字符最后出现的位置,O(n)时间
  2. 扫描阶段:从左往右扫描,维护当前片段的右边界end,遇到字符c就更新end = max(end, last[c]),当扫描位置i追上end时,片段完成

Step 4:选择武器

  • 选用:哈希表预处理 + 贪心扫描
  • 理由:
    • 哈希表:O(1)查询字符最后位置,避免重复搜索
    • 贪心:每次尽早切分片段(一旦i==end立即切分),保证片段数最多

🔑 模式识别提示:当题目出现"字符/元素的最后(或最早)出现位置"+"区间划分",优先考虑哈希表预处理 + 贪心扫描模式


🔑 解法一:暴力扫描(直觉法)

思路

每次遇到新字符,就搜索它在整个串中的最后位置,不断扩展片段右边界,直到当前位置追上边界。

图解过程

示例:s = "ababcbacadefegde"i=0开始:
i=0, s[0]='a', 找a的最后位置=8 → end=8
i=1, s[1]='b', 找b的最后位置=5 → end=max(8,5)=8
i=2, s[2]='a', 最后位置还是8 → end=8
...
i=8, s[8]='a', i==end → 片段1完成:[0~8] 长度9i=9开始:
i=9, s[9]='d', 找d的最后位置=15 → end=15
i=10, s[10]='e', 找e的最后位置=14 → end=15
...
i=15, s[15]='e', i==end → 片段2完成:[9~15] 长度7

结果:[9,7]

Python代码

from typing import List


def partitionLabels_brute(s: str) -> List[int]:
    """
    解法一:暴力扫描
    思路:每次都用rfind查找字符最后位置,扩展片段边界
    """
    result = []
    i = 0
    n = len(s)

    while i < n:
        end = i  # 当前片段的右边界

        # 扩展边界,直到包含所有当前片段内字符的最后位置
        j = i
        while j <= end:
            last_pos = s.rfind(s[j])  # 查找s[j]的最后位置
            end = max(end, last_pos)
            j += 1

        # 切分片段
        result.append(end - i + 1)
        i = end + 1

    return result


# ✅ 测试
print(partitionLabels_brute("ababcbacadefegdehijhklij"))  # 期望输出:[9,7,8]
print(partitionLabels_brute("eccbbbbdec"))  # 期望输出:[10]
print(partitionLabels_brute("abcd"))  # 期望输出:[1,1,1,1]

复杂度分析

  • 时间复杂度:O(n²) — 外层while循环O(n),内层while中每次rfind是O(n),总体O(n²)
    • 具体地说:如果n=500,可能需要约25万次操作
  • 空间复杂度:O(1) — 只用几个变量(不计结果数组)

优缺点

  • ✅ 思路直观,无需预处理
  • ✅ 空间占用少
  • ❌ 时间复杂度高,重复查找造成浪费
  • ❌ 不适合长字符串

🏆 解法二:哈希表预处理 + 贪心扫描(最优解)

优化思路

解法一的瓶颈在于重复调用rfind。我们可以用一次遍历预先记录每个字符的最后位置,然后一次扫描完成切分。

💡 关键想法:用O(n)时间预处理出字符位置字典,然后O(n)扫描,总体O(n),比暴力法快n倍!

图解过程

示例:s = "ababcbacadefegdehijhklij"

第1步:预处理,记录每个字符最后位置
  遍历字符串,建立字典:
  last = {
    'a': 8,   'b': 5,   'c': 7,
    'd': 14,  'e': 15,  'f': 11, 'g': 13,
    'h': 19,  'i': 22,  'j': 23,
    'k': 20,  'l': 21
  }

第2步:贪心扫描,动态扩展片段右边界
  start=0, end=0

  i=0: s[0]='a', end=max(0, last['a']=8)=8
  i=1: s[1]='b', end=max(8, last['b']=5)=8
  i=2: s[2]='a', end=max(8, 8)=8
  i=3: s[3]='b', end=max(8, 5)=8
  i=4: s[4]='c', end=max(8, last['c']=7)=8
  i=5: s[5]='b', end=max(8, 5)=8
  i=6: s[6]='a', end=max(8, 8)=8
  i=7: s[7]='c', end=max(8, 7)=8
  i=8: s[8]='a', end=max(8, 8)=8i==end → 切分片段[0~8],长度9
    start=9, end=9

  i=9:  s[9]='d',  end=max(9, last['d']=14)=14
  i=10: s[10]='e', end=max(14, last['e']=15)=15
  i=11: s[11]='f', end=max(15, last['f']=11)=15
  i=12: s[12]='e', end=max(15, 15)=15
  i=13: s[13]='g', end=max(15, last['g']=13)=15
  i=14: s[14]='d', end=max(15, 14)=15
  i=15: s[15]='e', end=max(15, 15)=15i==end → 切分片段[9~15],长度7
    start=16, end=16

  i=16: s[16]='h', end=max(16, last['h']=19)=19
  i=17: s[17]='i', end=max(19, last['i']=22)=22
  i=18: s[18]='j', end=max(22, last['j']=23)=23
  i=19: s[19]='h', end=max(23, 19)=23
  i=20: s[20]='k', end=max(23, last['k']=20)=23
  i=21: s[21]='l', end=max(23, last['l']=21)=23
  i=22: s[22]='i', end=max(23, 22)=23
  i=23: s[23]='j', end=max(23, 23)=23i==end → 切分片段[16~23],长度8

结果:[9, 7, 8] ✅

可视化片段:
  a b a b c b a c a | d e f e g d e | h i j h k l i j
  ←---- 片段1 ----→   ←-- 片段2 --→   ←--- 片段3 ---→
  字符集{a,b,c}      字符集{d,e,f,g}   字符集{h,i,j,k,l}

为什么这样是最优的?

为什么片段1必须到8才能切分?
  因为'a'最后出现在位置8,如果提前切分(如位置5),
  那么'a'会同时出现在两个片段中,违反题目要求。

为什么一到8就立即切分?
  因为位置8之前的所有字符(a,b,c)的最后出现位置都≤8,
  它们不会在8之后出现,所以可以立即切分,让片段尽可能短。

这就是贪心策略:一旦能切就切,保证片段数最多。

Python代码

def partitionLabels(s: str) -> List[int]:
    """
    解法二:哈希表预处理 + 贪心扫描(最优解)
    思路:先记录每个字符最后位置,再一次扫描动态扩展片段边界
    """
    # 第1步:预处理 - 记录每个字符最后出现的位置
    last = {}
    for i, char in enumerate(s):
        last[char] = i  # 不断更新,最终存的是最后位置

    # 第2步:贪心扫描 - 动态扩展片段右边界
    result = []
    start = 0  # 当前片段起点
    end = 0    # 当前片段右边界

    for i, char in enumerate(s):
        # 贪心:更新右边界为"当前片段内所有字符的最后位置的最大值"
        end = max(end, last[char])

        # 当扫描位置追上边界时,片段完成
        if i == end:
            result.append(end - start + 1)  # 记录片段长度
            start = end + 1  # 开启新片段

    return result


# ✅ 测试
print(partitionLabels("ababcbacadefegdehijhklij"))  # 期望输出:[9,7,8]
print(partitionLabels("eccbbbbdec"))  # 期望输出:[10]
print(partitionLabels("abcd"))  # 期望输出:[1,1,1,1]
print(partitionLabels("a"))  # 期望输出:[1]
print(partitionLabels("aaaa"))  # 期望输出:[4]

复杂度分析

  • 时间复杂度:O(n) — 两次线性遍历,第一次建字典O(n),第二次扫描O(n),总体O(n)
    • 具体地说:如果n=500,只需约1000次操作,比暴力法的25万次快250倍!
  • 空间复杂度:O(1) — 字典最多存26个小写字母,常数空间(不计结果数组)

为什么这是最优解?

  1. 时间已达理论下限:必须至少遍历一次字符串来确定字符位置,O(n)已是最优
  2. 空间也是最优:只用26个键的字典,O(1)常数空间
  3. 贪心策略正确性:一旦i==end就立即切分,保证每个片段最短,片段数最多
  4. 代码简洁高效:核心逻辑不到15行,易于理解和实现

⚡ 解法三:双指针变体(思路补充)

思路

本质与解法二相同,用不同的代码组织形式:记录片段的左右边界,逐步扩展。

Python代码

def partitionLabels_v2(s: str) -> List[int]:
    """
    解法三:双指针变体
    思路:显式维护片段的[left, right]边界
    """
    # 预处理:记录每个字符最后位置
    last = {char: i for i, char in enumerate(s)}

    result = []
    left, right = 0, 0

    for i, char in enumerate(s):
        right = max(right, last[char])  # 扩展右边界

        if i == right:  # 到达边界,切分片段
            result.append(right - left + 1)
            left = right + 1  # 下一个片段起点

    return result


# ✅ 测试
print(partitionLabels_v2("ababcbacadefegdehijhklij"))  # 期望输出:[9,7,8]

复杂度分析

  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

与解法二本质相同,只是代码风格不同。


🐍 Pythonic 写法

利用字典推导式和enumerate的简洁版:

def partitionLabels_pythonic(s: str) -> List[int]:
    """Pythonic写法:字典推导式 + 紧凑逻辑"""
    # 一行构建last字典
    last = {char: i for i, char in enumerate(s)}

    result = []
    start = end = 0

    for i, char in enumerate(s):
        end = max(end, last[char])
        if i == end:
            result.append(end - start + 1)
            start = end + 1

    return result

特点:

  • 用字典推导式{char: i for i, char in enumerate(s)}一行构建last
  • 逻辑紧凑,保持可读性
  • 适合向面试官展示Python语言功底

⚠️ 面试建议:先写解法二的标准版本展示清晰思路,再提Pythonic写法展示语言功底。面试官更看重你的算法设计能力,而非炫技。


📊 解法对比

维度解法一:暴力扫描🏆 解法二:哈希表+贪心(最优)解法三:双指针变体
时间复杂度O(n²)O(n) ← 时间最优O(n)
空间复杂度O(1)O(1) ← 空间最优O(1)
代码难度简单(但低效)简单(15行)简单
面试推荐⭐⭐⭐ ← 首选⭐⭐
适用场景仅作对比理解面试首选,工程实用思路补充

为什么解法二是最优解:

  • 时间O(n)已达理论最优:必须至少遍历一次字符串,不可能更快
  • 空间O(1)也是最优:字符集固定(26个字母),字典大小为常数
  • 预处理+贪心策略高效:一次预处理避免重复查找,贪心保证片段数最多
  • 代码清晰简洁:逻辑分两步(预处理→扫描),易于理解和实现

面试建议:

  1. 先用30秒简述暴力思路(O(n²)),表明你理解问题:"每次用rfind查找最后位置"
  2. 立即优化到🏆解法二(O(n)):"我们可以预处理出字符位置字典,避免重复查找"
  3. 重点画图讲解贪心过程:用[ababcbacadefegde]演示如何动态扩展边界
  4. 强调为什么这是最优:时间O(n)、空间O(1),预处理是关键优化点
  5. 对比类似题目:与"合并区间"类似,都是贪心地维护区间边界

🎤 面试现场

模拟面试中的完整对话流程,帮你练习"边想边说"。

面试官:请你解决一下这道划分字母区间的问题。

:(审题30秒)好的,这道题要求把字符串分成若干片段,每个字母只能出现在一个片段中,并且片段数要尽可能多。

我的第一个想法是,每次遇到字符就用rfind找它的最后位置,不断扩展片段边界,时间复杂度O(n²)。但这样会重复查找,效率不高。

更优的方法是哈希表预处理 + 贪心扫描:先用O(n)时间遍历一次,记录每个字符最后出现的位置,存到字典里。然后再遍历一次,维护当前片段的右边界end,遇到字符c就更新end = max(end, last[c]),当扫描位置i追上end时,说明片段内所有字符都不会再往后出现了,可以切分。这样时间O(n),空间O(1)。

面试官:很好,请写一下代码并解释。

:(边写边说)

def partitionLabels(s):
    # 第1步:预处理 - 记录每个字符最后位置
    last = {}
    for i, char in enumerate(s):
        last[char] = i

    # 第2步:贪心扫描 - 动态扩展片段边界
    result = []
    start = end = 0

    for i, char in enumerate(s):
        # 更新右边界为当前片段内所有字符的最后位置的最大值
        end = max(end, last[char])

        # 一旦扫描位置追上边界,切分片段
        if i == end:
            result.append(end - start + 1)
            start = end + 1

    return result

核心思想:

  1. 预处理阶段:建立字符→最后位置的映射,避免重复查找
  2. 贪心阶段:动态扩展片段右边界,一旦i==end就立即切分,保证片段最短

面试官:测试一下?

:用"ababcbaca"走一遍:

  • 预处理:last = {'a':8, 'b':5, 'c':7}
  • 扫描:
    • i=0, char='a', end=max(0,8)=8
    • i=1~7, end保持或更新,但都≤8
    • i=8, end=8, i==end → 切分片段[0~8],长度9 ✅

再测边界情况"abcd":

  • last = {'a':0, 'b':1, 'c':2, 'd':3}
  • 每个字符最后位置就是自己,所以每次i==end都立即切分
  • 结果:[1,1,1,1] ✅

面试官:为什么last字典的空间复杂度是O(1)而不是O(n)?

:因为字符集是固定的26个小写字母,不管字符串多长,字典最多存26个键值对,这是常数。所以空间复杂度是O(1)。如果题目改成Unicode字符,那就得按O(n)算了。

高频追问

追问应答策略
"还有更优解吗?"时间O(n)已经是最优(必须遍历字符串),空间O(1)也是最优(字符集固定)。无法再优化。
"如果字符集非常大(如Unicode)?"哈希表空间会变成O(k),k是字符集大小,但算法思路不变。可以用defaultdict简化。
"能否只遍历一次?"不行,必须先知道每个字符的最后位置才能正确切分。虽然是两次遍历,但都是O(n),总体还是O(n)。
"这道题和'合并区间'有什么关系?"相似点:都是贪心维护区间边界。区别:'合并区间'是给定区间求并集,'划分字母区间'是根据字符位置动态生成区间边界。
"如果要求返回片段本身而不是长度?"只需修改返回语句:result.append(s[start:end+1]),其他逻辑不变。

🎓 知识点总结

Python技巧卡片 🐍

# 技巧1:字典推导式构建映射
last = {char: i for i, char in enumerate(s)}

# 技巧2:enumerate同时获取索引和值
for i, char in enumerate(s):
    ...

# 技巧3:max动态更新最大值
end = max(end, last[char])

# 技巧4:多变量同时赋值初始化
start = end = 0

# 技巧5:字典的键存在性检查(本题未用,但相关)
if char in last:  # O(1)查询
    ...

💡 底层原理(选读)

为什么哈希表查询是O(1)?

Python的dict底层是用哈希表(Hash Table)实现的:

  1. 键通过哈希函数转化为数组索引:index = hash(key) % array_size
  2. 直接通过索引访问值,不需要遍历,所以是O(1)
  3. 哈希冲突用链表或开放寻址解决,平均情况仍是O(1)

本题中:

  • 键:'a'~'z' 共26个字符
  • 值:最后出现的位置(整数)
  • 查询last[char]是O(1),比s.rfind(char)的O(n)快n倍

贪心算法的适用场景:

  • 局部最优 = 全局最优:每次选择当前最优的决策,不会影响后续的最优性
  • 无后效性:当前决策不依赖未来状态

本题中:

  • 局部最优:一旦i==end就立即切分,让当前片段最短
  • 为什么全局最优:因为所有字符的最后位置已知,提前切分不会破坏后续片段的合法性
  • 无后效性:已切分的片段不影响后续片段的划分

与"合并区间"(LeetCode 56)的对比:

维度合并区间划分字母区间
输入给定区间列表字符串(隐式生成区间)
操作合并重叠区间切分非重叠区间
预处理按起点排序记录字符最后位置
核心比较当前区间与前一区间动态扩展片段边界
共同点贪心维护区间边界贪心维护区间边界

算法模式卡片 📐

  • 模式名称:字符位置哈希 + 贪心区间切分
  • 适用条件:
    • 需要频繁查询元素的位置(首次、最后、所有)
    • 区间划分/合并问题
    • 要求尽可能多/少的区间数
  • 识别关键词:
    • "每个字母/元素最多出现在一个..."
    • "尽可能多的片段"
    • "最后出现的位置"
  • 模板代码:
def partition_template(s: str) -> List[int]:
    # 第1步:预处理 - 记录关键信息(首次/最后位置)
    last = {}
    for i, char in enumerate(s):
        last[char] = i  # 或first[char] = first.get(char, i)

    # 第2步:贪心扫描 - 动态维护区间边界
    result = []
    start = end = 0

    for i, char in enumerate(s):
        end = max(end, last[char])  # 扩展右边界

        if i == end:  # 到达边界,切分
            result.append(end - start + 1)
            start = end + 1

    return result

易错点 ⚠️

  1. 预处理阶段错误:

    • ❌ 错误:用列表记录所有出现位置positions['a'] = [0, 2, 8]
    • ✅ 正确:只记录最后位置last['a'] = 8
    • 原因:只需最后位置就能确定片段边界,记录所有位置浪费空间
  2. 边界更新错误:

    • ❌ 错误:end = last[char](直接赋值)
    • ✅ 正确:end = max(end, last[char])(取最大值)
    • 原因:片段内可能有多个字符,边界是所有字符最后位置的最大值,不能被后面的小值覆盖
  3. 切分条件错误:

    • ❌ 错误:if i >= end(用>=)
    • ✅ 正确:if i == end(用==)
    • 原因:i不可能>end,因为每次循环都会更新end到至少i的位置
  4. 忘记更新start:

    • ❌ 错误:切分后只append长度,不更新start
    • ✅ 正确:start = end + 1
    • 原因:下一个片段从end+1开始,不更新会导致长度计算错误
  5. 混淆"尽可能多"和"尽可能少":

    • 题目要求片段数尽可能多 = 每个片段尽可能短
    • 策略:一旦i==end立即切分,不等待
    • 如果要求片段数尽可能少,那就要尽量延迟切分(不同问题)

🏗️ 工程实战(选读)

这个算法思想在真实项目中的应用,让你知道"学了有什么用"。

  • 场景1:日志文件分片 在分布式系统中,日志按用户ID记录。为了并行处理,需要把日志分成多个片段,要求同一用户的日志必须在同一片段中,且片段数尽可能多(便于并行)。这道题的算法可以直接应用:预处理出每个用户ID最后出现的行号,然后贪心切分。

  • 场景2:数据库分区 在数据库水平分区(Sharding)中,需要把数据按某个键(如user_id)分配到不同分区,要求同一键的所有记录在同一分区。预先统计键的分布范围,然后用贪心算法划分区间,最小化跨分区查询。

  • 场景3:视频剪辑自动分段 在视频编辑软件中,根据字幕/台词把视频自动分段,要求同一个人的连续台词在同一段中。预处理出每个说话人最后出现的时间戳,然后贪心切分时间轴,生成多个视频片段。


🏋️ 举一反三

完成本课后,试试这些同类题目来巩固知识:

题目难度相关知识点提示
LeetCode 56. 合并区间Medium贪心,排序+区间合并先按起点排序,然后贪心合并重叠区间,与本题互为对偶
LeetCode 435. 无重叠区间Medium贪心,按终点排序求最少删除多少区间使剩余无重叠,贪心选择终点最早的
LeetCode 452. 用最少的箭引爆气球Medium贪心,区间交集本质是"合并区间"变体,贪心选择射击位置
LeetCode 986. 区间列表的交集Medium双指针,区间合并给定两个区间列表,求交集,用双指针扫描
LeetCode 228. 汇总区间Easy区间表示给定有序数组,找出连续区间,用双指针/贪心

📝 课后小测

试试这道变体题,不要看答案,自己先想5分钟!

题目:给定字符串s,找出最长的子串,使得子串中每个字符最多出现一次。返回该子串的长度。

例如:s = "abcabcbb",最长子串是"abc",长度为3。

💡 提示(实在想不出来再点开)

这题是滑动窗口经典题(第14课:无重复字符的最长子串)。用哈希表记录窗口内字符的位置,当发现重复字符时,收缩左边界。与本题的区别是:本题要切分多个片段,那题要找一个最长片段

✅ 参考答案
def lengthOfLongestSubstring(s: str) -> int:
    """
    最长无重复子串:滑动窗口 + 哈希表
    思路:维护窗口[left, right],用哈希表记录字符位置
    """
    seen = {}  # 记录字符最近出现的位置
    left = 0
    max_len = 0

    for right, char in enumerate(s):
        # 如果字符在窗口内重复,收缩左边界
        if char in seen and seen[char] >= left:
            left = seen[char] + 1  # 左边界移到重复字符的下一个位置

        seen[char] = right  # 更新字符位置
        max_len = max(max_len, right - left + 1)  # 更新最大长度

    return max_len


# 测试
print(lengthOfLongestSubstring("abcabcbb"))  # 期望输出:3 ("abc")
print(lengthOfLongestSubstring("bbbbb"))  # 期望输出:1 ("b")
print(lengthOfLongestSubstring("pwwkew"))  # 期望输出:3 ("wke")

核心思路:

  1. 滑动窗口[left, right]维护当前无重复子串
  2. 哈希表seen记录字符最近出现的位置
  3. 当发现重复字符时,收缩左边界到重复位置的下一个
  4. 持续更新最大长度

与本课题目的对比:

维度本课(划分字母区间)无重复最长子串
目标切分多个片段一个最长片段
预处理记录字符最后位置不需要预处理
核心算法贪心扩展边界滑动窗口动态调整
复杂度O(n)O(n)

如果这篇内容对你有帮助,推荐收藏 AI Compass:github.com/tingaicompa… 更多系统化题解、编程基础和 AI 学习资料都在这里,后续复习和拓展会更省时间。