【每日算法】LeetCode 763. 划分字母区间(贪心算法)

对前端开发者而言,学习算法绝非为了"炫技"。它是你从"页面构建者"迈向"复杂系统设计者"的关键阶梯。它将你的编码能力从"实现功能"提升到"设计优雅、高效解决方案"的层面。从现在开始,每天投入一小段时间,结合前端场景去理解和练习,你将会感受到自身技术视野和问题解决能力的质的飞跃。------ 算法:资深前端开发者的进阶引擎

LeetCode 763. 划分字母区间

1. 题目描述

LeetCode 763. 划分字母区间要求:给定一个字符串 S,将字符串划分为尽可能多的片段,使得每个字母最多出现在一个片段中。返回一个表示每个片段长度的列表。

1.1 示例说明

  • 输入 : S = "ababcbacadefegdehijhklij"
  • 输出 : [9,7,8]
  • 解释 :
    划分结果为 "ababcbaca""defegde""hijhklij"。每个字母(如 abc 等)在同一个片段中出现,且不会跨片段重复。

1.2 注意点

  • 字符串 S 只包含小写英文字母。
  • 划分需要最大化片段数量,即每个片段尽可能短,但前提是满足字母不跨片段的条件。
  • 输出是片段长度的列表,顺序与划分顺序一致。

2. 问题分析

对于前端开发者,这个问题可以类比于组件或模块的拆分:在大型前端项目中,我们希望将代码拆分为独立模块,每个模块负责特定功能,且依赖关系清晰(类似字母不跨片段)。这里,字符串中的字母代表"依赖",我们需要找到最小粒度的划分。

关键挑战在于如何高效确定划分点。暴力枚举所有可能的划分点会指数级增长,不可行。因此,我们需要利用字符串特性进行优化。

3. 解题思路

3.1 核心思路:贪心算法

贪心算法通过局部最优选择达到全局最优。对于此题:

  1. 预处理:记录每个字母在字符串中最后出现的位置。
  2. 遍历字符串:维护当前片段的开始和结束位置。对于每个字符,更新当前片段的结束位置为当前字符最后出现位置的最大值。
  3. 划分时机:当遍历索引等于当前片段的结束位置时,表示一个片段结束,记录其长度,并开始下一个片段。

这种方法确保每个片段包含所有必需字母,且不重叠,从而最大化片段数量。

3.2 复杂度分析

  • 时间复杂度: O(n),其中 n 是字符串长度。需要两次遍历:一次记录最后位置,一次划分片段。
  • 空间复杂度: O(1),因为字符集为小写字母,使用固定大小的数组(长度 26)存储最后位置。
  • 最优解: 贪心算法是最优解,因为它在线性时间内解决问题,且无需额外数据结构。

3.3 其他思路对比

  • 暴力枚举: 枚举所有可能的划分点组合,检查每个组合是否满足条件。时间复杂度 O(2^n),空间复杂度 O(n),不适用于大规模输入。
  • 动态规划: 可设计状态表示划分,但复杂度较高,贪心更简洁高效。

因此,贪心算法是推荐方法。

4. 代码实现

以下用 JavaScript 实现贪心算法,代码包含详细注释和步骤分解。

4.1 JavaScript 实现

javascript 复制代码
/**
 * LeetCode 763. 划分字母区间
 * 贪心算法实现:基于字母最后出现位置进行划分
 * @param {string} S - 输入字符串,只包含小写字母
 * @return {number[]} - 片段长度列表
 */
var partitionLabels = function(S) {
    // 步骤1: 预处理,记录每个字母最后出现的位置
    const lastOccurrence = new Array(26).fill(-1); // 26个小写字母
    const charCodeA = 'a'.charCodeAt(0); // 'a'的ASCII码,用于索引映射
    
    for (let i = 0; i < S.length; i++) {
        const charIndex = S.charCodeAt(i) - charCodeA; // 将字符映射到0-25
        lastOccurrence[charIndex] = i; // 更新最后出现位置
    }
    
    // 步骤2: 遍历字符串,进行贪心划分
    const result = []; // 存储片段长度
    let start = 0; // 当前片段的开始索引
    let end = 0;   // 当前片段的结束索引
    
    for (let i = 0; i < S.length; i++) {
        const charIndex = S.charCodeAt(i) - charCodeA;
        // 更新当前片段的结束位置:取当前字符最后出现位置的最大值
        end = Math.max(end, lastOccurrence[charIndex]);
        
        // 当遍历到当前片段的结束位置时,表示一个片段完成
        if (i === end) {
            // 计算片段长度并添加到结果
            result.push(end - start + 1);
            // 开始下一个片段,更新start为下一个索引
            start = i + 1;
        }
    }
    
    return result;
};

// 示例测试
console.log(partitionLabels("ababcbacadefegdehijhklij")); // 输出: [9,7,8]
console.log(partitionLabels("eccbbbbdec")); // 输出: [10]

4.2 步骤分解说明

  1. 预处理阶段:

    • 创建一个长度为 26 的数组 lastOccurrence,初始化为 -1。
    • 遍历字符串 S,对于每个字符,计算其索引(通过 ASCII 码减去 'a' 的码值),并更新数组对应位置为当前索引。这确保了数组存储每个字母的最后出现位置。
  2. 划分阶段:

    • 初始化 startend 为 0,分别表示当前片段的开始和结束索引。
    • 再次遍历字符串 S
      • 对于每个字符,获取其最后出现位置,并更新 endMath.max(end, lastOccurrence[charIndex])。这保证了当前片段包含所有已遍历字符的完整出现。
      • 检查当前索引 i 是否等于 end。如果相等,说明当前片段已覆盖所有必需字母,可以划分:
        • 计算片段长度:end - start + 1,并推入结果数组。
        • 更新 starti + 1,开始下一个片段。
    • 返回结果数组。

4.3 前端场景联想

  • 此算法类似前端中代码分割(Code Splitting):根据模块依赖关系动态拆分代码块,以优化加载性能。贪心策略帮助找到最小粒度分割点,提升应用效率。

5. 各实现思路的复杂度、优缺点对比表格

思路 时间复杂度 空间复杂度 优点 缺点 适用场景
贪心算法 O(n) O(1)(固定26长度数组) 高效、简洁、最优解 假设字符集固定(小写字母) 大多数情况,尤其是字符集有限
暴力枚举 O(2^n) O(n) 简单直观,易实现 指数级复杂度,不适用于长字符串 仅用于教学或极小输入
动态规划 O(n^2) 或更高 O(n^2) 可处理更复杂约束 过度设计,贪心更优 当贪心不适用时(但此题贪心有效)
  • 推荐: 贪心算法是此题的标准解法,兼顾性能和可读性。

6. 总结

6.1 通用解题模板

此类划分问题通常遵循贪心模板

  1. 预处理: 收集关键信息(如最后出现位置、频率等)。
  2. 遍历维护: 使用指针或变量维护当前区间状态(如开始和结束)。
  3. 决策点: 在特定条件(如索引等于结束位置)下进行划分或更新。
  4. 累积结果: 记录每次划分的输出。

6.2 LeetCode 类似题目

  • 56. 合并区间: 给定区间列表,合并重叠区间。类似贪心思路,按起始排序后合并。
  • 435. 无重叠区间: 选择不重叠区间的最大数量,贪心按结束时间排序。
  • 452. 用最少数量的箭引爆气球: 区间问题,贪心求最小交集点。
  • 122. 买卖股票的最佳时机 II: 贪心累积利润,类似片段划分思想。
相关推荐
zore_c2 小时前
【数据结构】二叉树初阶——超详解!!!(包含二叉树的实现)
c语言·开发语言·数据结构·经验分享·笔记·算法·链表
幺零九零零2 小时前
全栈程序员-前端第一节-npm 是什么?
前端·npm·node.js
尋有緣2 小时前
力扣1225-报告系统状态的连续日期
数据库·sql·算法·leetcode·oracle
Rysxt_2 小时前
UniApp pages.json 配置完全指南
开发语言·前端·javascript
爱学大树锯2 小时前
【快刷面试-高并发锁篇】- 基于票务系统在不同服务器,分布式场景中该如何解决
服务器·分布式·面试
代码游侠2 小时前
学习笔记——网络基础
linux·c语言·网络·笔记·学习·算法
BD_Marathon2 小时前
Vue3_组件传参问题
前端·javascript·vue.js
Coffeeee2 小时前
Android15适配之edge-to-edge和16kb到底咋适配
android·前端·android studio
热爱生活的五柒2 小时前
深度聚类(Deep Clustering)与度量学习(Metric Learning)的共同点和不同点
人工智能·算法·机器学习