欢迎光临
我们一直在努力

763. 划分字母区间

763. 划分字母区间

中等

提示

给你一个字符串 s 。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。例如,字符串 "ababcc" 能够被分为 ["abab", "cc"],但类似 ["aba", "bcc"] 或 ["ab", "ab", "cc"] 的划分是非法的。

注意,划分结果需要满足:将所有划分结果按顺序连接,得到的字符串仍然是 s 。

返回一个表示每个字符串片段的长度的列表。

示例 1:

输入:s = "ababcbacadefegdehijhklij"
输出:[9,7,8]
解释:
划分结果为 "ababcbaca"、"defegde"、"hijhklij" 。
每个字母最多出现在一个片段中。
像 "ababcbacadefegde", "hijhklij" 这样的划分是错误的,因为划分的片段数较少。

示例 2:

输入:s = "eccbbbbdec"
输出:[10]

提示:

  • 1 <= s.length <= 500
  • s 仅由小写英文字母组成

📝 核心笔记:划分字母区间 (Partition Labels)

1. 核心思想 (一句话总结)

“橡皮筋扩容法:先用雷达扫描一遍记录每个字母的‘最终归宿’;然后再走一遍,遇到谁就把当前区间的边界‘撑’大到它的归宿位置,直到走到边界为止,咔嚓一刀。”

  • 预处理:必须先知道每个字母最后出现在哪 (last[])。
  • 贪心扩充:遍历字符时,当前片段的结束点 end 必须能够覆盖当前字符的 last 位置。
  • 切割时机:当遍历指针 i 追上了扩充后的边界 end,说明当前片段里的所有字符都已处理完毕,可以安全切割。
2. 算法流程 (Greedy Scan)
  • 打点 (Mapping):
      • 遍历字符串,用数组 last[26] 记录每个字母最后一次出现的下标。
      • 例如 abca,a 的 last 是 3。
  • 扫描 (Scanning):
      • 维护两个指针:start (当前片段起点) 和 end (当前片段预估的终点)。
      • 遍历字符串 i 从 0 到 n-1。
  • 扩界 (Expanding):
      • 对于字符 s[i],查找它的 last 位置。
      • end = Math.max(end, last[s[i]])。如果这个字符最后出现在很后面,边界就被迫撑大了。
  • 收割 (Cutting):
      • 如果 i == end,说明我们终于走到了目前所需的最远边界。
      • 记录长度 end – start + 1。
      • 重置起点 start = i + 1。
    🔍 代码回忆清单

    // 题目:LC 763. Partition Labels
    class Solution {
    public List<Integer> partitionLabels(String S) {
    char[] s = S.toCharArray(); // 转数组提速
    int n = s.length;

    // 1. 预处理:记录每个字母最后出现的下标
    // 就像记录每个人的"离场时间"
    int[] last = new int[26];
    for (int i = 0; i < n; i++) {
    last[s[i] – 'a'] = i;
    }

    List<Integer> ans = new ArrayList<>();
    int start = 0; // 当前片段起点
    int end = 0; // 当前片段最远能延伸到的地方

    // 2. 二次遍历:贪心切分
    for (int i = 0; i < n; i++) {
    // 3. 扩充边界
    // 只要当前字符的"离场时间"比 end 晚,我们就得被迫延长时间
    end = Math.max(end, last[s[i] – 'a']);

    // 4. 到达边界
    // 当前位置 i 追上了 end,说明这一段里的所有人都离场了
    if (end == i) {
    ans.add(end – start + 1); // 记录长度
    start = i + 1; // 准备开启下一段
    }
    }
    return ans;
    }
    }

    ⚡ 快速复习 CheckList (易错点)
    • [ ] 为什么是 end == i 而不是 end == last[s[i]]?
      • 因为 end 是当前片段内所有字符的 last 值的最大值。
      • 可能 s[i] 的 last 很早就在前面结束了,但前面某个字符把 end 撑到了后面。必须等到指针 i 真正走到那个最远的 end,才能保证片段完整。
    • [ ] start 的更新逻辑?
      • 切割后,下一段的起点自然是 i + 1。
      • 计算长度公式:end – start + 1 (闭区间长度)。
    • [ ] 时间复杂度?
      • $O(N)$。字符串被扫描了两次(一次记 last,一次切分)。
      • 空间复杂度 $O(1)$ (固定 26 大小的数组)。
    🖼️ 数字演练

    S = "abaccc"

    last 数组: a->2, b->1, c->5

  • i=0, char='a':
      • last['a'] = 2.
      • end = max(0, 2) = 2. (边界撑到 2)
      • i(0) != end(2), 继续。
  • i=1, char='b':
      • last['b'] = 1.
      • end = max(2, 1) = 2. (边界保持 2)
      • i(1) != end(2), 继续。
  • i=2, char='a':
      • last['a'] = 2.
      • end = max(2, 2) = 2.
      • i(2) == end(2)! 切割!
      • 长度 2 – 0 + 1 = 3 ("aba"). start 变为 3.
  • i=3, char='c':
      • last['c'] = 5.
      • end = max(0, 5) = 5. (边界撑到 5)
      • i(3) != end(5).
  • … i=4, i=5:
      • end 始终维持 5。
      • 当 i=5 时,i(5) == end(5)。切割!
      • 长度 5 – 3 + 1 = 3 ("ccc").

    最终结果: [3, 3]。

    赞(0)
    未经允许不得转载:171主机测评 » 763. 划分字母区间
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址