LeetCode 2405. 子字符串的最优划分

LeetCode 2405. 子字符串的最优划分

1. 题目链接与难度

2405. 子字符串的最优划分 | 难度:中等


2. 白话题意重述

场景类比

想象你是一个图书馆管理员,现在有一长串字母序列需要整理到书架上。

规则是

  • 每个书架(子字符串)只能放不同的字母,不能有重复
  • 你想让书架数量尽可能少

最直接的做法

  • 从左到右依次放字母
  • 如果当前字母已经在当前书架上出现过,就新开一个书架,把这个字母放进去
  • 重复这个过程

3. 解法全景与决策

3.1 为什么直觉解法就是最优解?

这道题比较特殊------贪心策略就是最优解,不需要"优化"。

让我们用 s = "abacaba" 来手动模拟:

复制代码
字母流: a b a c a b a
        ↓
书架1:  a b        (遇到第二个 'a',冲突!)
书架2:        c a  (遇到第二个 'b',冲突!)
书架3:          b
书架4:            a

为什么贪心是最优的?

这里有一个关键观察:一旦当前字母在当前子字符串中已经存在,继续延长当前子字符串就完全不可能了,因为题目硬性要求"字符唯一"。

所以我们的选择只有:

  1. 开启新子字符串(必须)
  2. 不可能继续(只有分割一条路)

既然"必须分割"是唯一选择,那么:

  • 让当前子字符串尽可能长(直到遇到冲突为止)
  • 分割后立即开始新子字符串

这就是最优策略,没有更好的做法了。

3.2 核心数据结构

我们需要一个数据结构,能快速判断"某个字符是否已在当前子字符串中出现过"。

选择:哈希集合(unordered_set)

  • count(ch) → O(1) 时间内判断字符是否已存在
  • insert(ch) → O(1) 时间添加新字符
  • clear() → O(1) 时间清空,开始新的子字符串

3.3 算法流程图

复制代码
开始遍历
    │
    ▼
读取字符 ch
    │
    ├─── ch 不在 set 中 ──→ 加入 set,继续下一个字符
    │
    └─── ch 已在 set 中 ──→ 计数器+1,清空 set,加入 ch,继续
    │
    ▼
遍历结束
    │
    ▼
返回计数器

4. 代码实现

理解了上述思路后,代码就很好写了:

cpp 复制代码
class Solution {
public:
    int partitionString(string s) {
        unordered_set<char> seen;  // 记录当前子字符串中出现过的字符
        int count = 1;             // 至少有1个子字符串
        
        for (char ch : s) {
            if (seen.count(ch)) {      // 如果字符已存在,说明必须分割
                count++;               // 新增一个子字符串
                seen.clear();          // 清空,重新开始计数
            }
            seen.insert(ch);           // 将当前字符加入集合
        }
        
        return count;
    }
};

复杂度分析

维度 复杂度 通俗解释
时间复杂度 O(n) 每个字符只遍历一次
空间复杂度 O(1) 最多存储26个不同字母(英文字母有限集)

代码细节解释

  1. 初始化 count = 1:即使字符串为空也有1个划分?不,空串返回0。但对于非空串,至少有1个划分
  2. 冲突时 count++clear():先增加计数表示新开一个子字符串,然后清空集合,再把当前字符放进去
  3. seen.count(ch) 判断顺序:先判断再插入,确保冲突后立即开始新的计数周期

5. 易错点与防坑指南

易错点一:初始化陷阱

cpp 复制代码
// ❌ 错误:空字符串会返回1
int count = 1;

// ✅ 正确:根据是否为空字符串动态处理
int count = s.empty() ? 0 : 1;

分析 :如果 s = ""(空字符串),按照题意"划分成零个子字符串",正确答案是 0。


易错点二:清空顺序陷阱

cpp 复制代码
// ❌ 错误:先插入再清空,当前字符会丢失
seen.insert(ch);
if (seen.count(ch)) {
    count++;
    seen.clear();
}

// ✅ 正确:先清空,再插入当前字符
if (seen.count(ch)) {
    count++;
    seen.clear();  // 清空后 ch 不在集合中
}
seen.insert(ch);  // 无论是否分割,都要把 ch 放进去

分析:冲突时必须把当前字符放到新的子字符串中,不能丢失。


6. 思考题与同类扩展

思考题

如果题目改成"每个子字符串中字符出现次数不超过 K 次"(K > 1),我们的代码需要怎么改?

提示 :把 seen.count(ch) > 0 改成 seen.count(ch) >= K,并且用一个哈希表计数而不只是集合。


推荐扩展题目

题号 题目名称 推荐理由
128. 最长连续序列 哈希集合的经典应用,本题是"找最长无重复段",本题是"找最少分割数"
3. 无重复字符的最长子串 滑动窗口处理无重复字符,本质上是同一类问题的不同问法

总结

这道题的核心思想就一句话:一旦遇到重复字符,就必须分割,且让当前段尽可能长

这就是"贪心"------每一步都做当前最优选择(当前字符能加就加,不能加就开新段),最终得到全局最优解。

相关推荐
数智工坊1 小时前
视觉-语言-动作模型解剖学:从模块、里程碑到核心挑战
论文阅读·人工智能·深度学习·算法·transformer
有点。2 小时前
C++(枚举法一练习题)
开发语言·c++·算法
basketball6162 小时前
C++ 单例模式完全指南:从饿汉式到现代 C++ 的最佳实践
java·c++·单例模式
黎阳之光2 小时前
视听融合新范式!黎阳之光打破视觉边界,声影协同赋能全域智慧管控
大数据·人工智能·物联网·算法·数字孪生
sheeta19982 小时前
LeetCode 每日一题笔记 日期:2026.05.19 题目:2540. 最小公共值
笔记·leetcode·排序算法
玖釉-2 小时前
栈——栈的定义及基本操作
c++·windows·算法·图形渲染
不想写代码的星星2 小时前
C++ 内存序六件套:从完全同步到爱咋咋地
c++
ゆづき3 小时前
Java 初学者入门指南:常见问题 + 核心知识点 + 进阶 20 道练习题
java·开发语言·学习·算法·水题
Evand J3 小时前
【课题推荐】强跟踪UKF算法,三维非线性状态量和观测量,附MATLAB代码测试结果
开发语言·算法·matlab