【LeetCode热题100道笔记】划分字母区间

题目描述

给你一个字符串 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 仅由小写英文字母组成

思考

核心是确保每个划分的片段内所有字符仅出现在该片段中,利用字符最后出现的位置确定片段边界:

  • 一个片段的结束位置,必须是该片段中所有字符在整个字符串中最后出现位置的最大值(否则会有字符出现在后续片段)。
  • 先预处理记录每个字符的最后出现索引,再通过一次遍历动态追踪当前片段的边界,实现线性时间划分。

算法过程

  1. 预处理字符最后位置

    • 遍历字符串s,用哈希表map记录每个字符最后出现的索引(map.get(c)即为字符cs中最后出现的位置)。
  2. 动态划分片段

    • 初始化start = 0(当前片段起始索引)、end = 0(当前片段结束索引)。
    • 再次遍历字符串,对每个索引i
      • 用当前字符的最后位置更新endend = max(end, map.get(s[i])),确保包含当前字符的所有出现)。
      • i === end时,说明当前片段已包含所有该包含的字符(无字符会出现在后续),计算片段长度(end - start + 1)并加入结果,同时更新start = i + 1(下一片段起始)。
  3. 返回结果:收集的片段长度数组即为答案。

时空复杂度

  • 时间复杂度:O(n),n为字符串长度。两次遍历字符串(预处理和划分),每次均为线性时间,哈希表操作是O(1)。
  • 空间复杂度:O(1),哈希表存储的字符数量有限(如小写字母最多26个),空间不随n增长。

代码

/**
 * @param {string} s
 * @return {number[]}
 */
var partitionLabels = function(s) {
    const n = s.length;
    const result = [];
    const map = new Map();

    for (let i = 0; i < n; i++) {
        map.set(s[i], i);     
    }

    let start = 0, end = 0;
    for (let i = 0; i < n;  i++) {
        let endIndex = map.get(s[i]);
        end = Math.max(end, endIndex);
        if (i === end) {
            result.push(end - start + 1);
            start = i + 1;
        }
    }
    
    return result;
};
### C++ 中实现划分字母区间算法 在 C++ 中实现划分字母区间的核心思路是利用贪心算法,通过记录每个字符的最后出现位置来动态调整当前子串的最大范围。以下是基于引用内容和专业知识的具体实现方法。 #### 算法描述 为了实现划分字母区间的功能,可以通过以下方式完成: 1. **预处理阶段**:构建一个数组 `last_pos` 或类似的结构,用于存储字符串中每个字符的最后一个索引位置[^1]。 2. **遍历字符串**:依次扫描字符串中的每个字符,并维护当前子串的起始位置 `start` 和结束位置 `end`。 3. **更新最大范围**:对于每个字符,将其对应的 `last_pos` 与当前的 `end` 取较大值作为新的 `end`。 4. **分割条件**:当当前索引等于 `end` 时,意味着找到了一个新的分区,此时将该分区的长度存入结果列表,并重新设置下一个分区的起点。 #### 完整代码示例 下面是一个完整的 C++ 实现: ```cpp #include <iostream> #include <vector> using namespace std; class Solution { public: vector<int> partitionLabels(string s) { // Step 1: Record the last occurrence of each character. vector<int> last_pos(26, -1); for (int i = 0; i < s.length(); ++i) { last_pos[s[i] - 'a'] = i; } // Step 2: Traverse the string and find partitions. vector<int> result; int start = 0, end = 0; for (int i = 0; i < s.length(); ++i) { end = max(end, last_pos[s[i] - 'a']); if (i == end) { result.push_back(end - start + 1); start = i + 1; } } return result; } }; int main() { Solution solution; string input_str = "ababcbacadefegdehijhklij"; vector<int> output = solution.partitionLabels(input_str); // Print the results. for (auto len : output) { cout << len << " "; } cout << endl; return 0; } ``` #### 关键点解析 - 使用了一个大小为 26 的数组 `last_pos` 来保存每种字母的最后出现位置[^1][^2]。 - 在主循环中不断更新当前分区的终点 `end`,直到发现某一点满足 `i == end`,即找到一个合法的分区[^3]。 - 结果以向量形式返回,其中每个元素代表对应分区的长度。 此方法的时间复杂度为 O(n),空间复杂度为 O(1)[^2],因为它只依赖于固定的额外内存开销(如 `last_pos` 数组)。 --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值