【LeetCode: 1358. 包含所有三种字符的子字符串数目 + 滑动窗口】

|-----------|
| 🚀 算法题 🚀 |

🌲 算法刷题专栏 | 面试必备算法 | 面试高频算法 🍀

🌲 越难的东西,越要努力坚持,因为它具有很高的价值,算法就是这样✨

🌲 作者简介:硕风和炜,CSDN-Java领域优质创作者🏆,保研|国家奖学金|高中学习JAVA|大学完善JAVA开发技术栈|面试刷题|面经八股文|经验分享|好用的网站工具分享💎💎💎

🌲 恭喜你发现一枚宝藏博主,赶快收入囊中吧🌻

🌲 人生如棋,我愿为卒,行动虽慢,可谁曾见我后退一步?🎯🎯

|-----------|
| 🚀 算法题 🚀 |

🍔 目录

    • [🚩 题目链接](#🚩 题目链接)
    • [⛲ 题目描述](#⛲ 题目描述)
    • [🌟 求解思路&实现代码&运行结果](#🌟 求解思路&实现代码&运行结果)
      • [⚡ 滑动窗口](#⚡ 滑动窗口)
        • [🥦 题目要求](#🥦 题目要求)
        • [🥦 核心逻辑拆解](#🥦 核心逻辑拆解)
        • [🥦 测试案例分析](#🥦 测试案例分析)
        • [🥦 实现代码](#🥦 实现代码)
        • [🥦 关键原理说明(为什么 cnt += left 是对的)](#🥦 关键原理说明(为什么 cnt += left 是对的))
        • [🥦 运行效率](#🥦 运行效率)
        • [🥦 思考](#🥦 思考)
    • [💬 共勉](#💬 共勉)

🚩 题目链接

⛲ 题目描述

给你一个字符串 s ,它只包含三种字符 a, b 和 c 。

请你返回 a,b 和 c 都 至少 出现过一次的子字符串数目。

示例 1:

输入:s = "abcabc"

输出:10

解释:包含 a,b 和 c 各至少一次的子字符串为 "abc", "abca", "abcab", "abcabc", "bca", "bcab", "bcabc", "cab", "cabc" 和 "abc" (相同字符串算多次)。
示例 2:

输入:s = "aaacb"

输出:3

解释:包含 a,b 和 c 各至少一次的子字符串为 "aaacb", "aacb" 和 "acb" 。
示例 3:

输入:s = "abc"

输出:1

提示:

3 <= s.length <= 5 x 10^4

s 只包含字符 a,b 和 c 。

🌟 求解思路&实现代码&运行结果


⚡ 滑动窗口

🥦 题目要求
  1. 题目要求统计同时包含 a、b、c 三种字符的所有子串数量。
  2. 核心技巧:滑动窗口维护区间 left, right,保证窗口不满足同时有 a/b/c,通过 left 的值快速算出以 right 为右端点的合法子串总数。
🥦 核心逻辑拆解
  1. 右指针 right 逐个遍历字符,不断扩大右边界,记录窗口内 a/b/c 的出现次数num0,num1,num2
  2. 只要当前窗口 left,right 三种字符齐全,就不断右移 left、减少左侧字符计数,直到窗口缺少至少一种字符。
  3. 此时所有起点在0 ~ left-1、终点为 right 的子串,一定都同时包含 a/b/c,合法子串数量正好等于 left,累加到结果 cnt。
  4. 遍历完所有 right,累加总和就是答案。
🥦 测试案例分析

举个直观例子:s = "abcabc"

  • right=2(字符 c):进入 while 循环收缩 left 到 1,cnt += 1
  • right=3(字符 a):继续收缩 left到 2,cnt += 2
  • right=4(字符 b):收缩 left 到 3,cnt +=3
  • right=5(字符 c):收缩 left到 4,cnt +=4

总和 1+2+3+4 = 10,和样例输出一致。

🥦 实现代码
java 复制代码
class Solution {
    public int numberOfSubstrings(String s) {
        int n = s.length();
        int cnt = 0;
        int[] num = new int[3];
        int left = 0;
        for (int right = 0; right < n; right++) {
            num[s.charAt(right) - 'a']++;
            while (num[0] > 0 && num[1] > 0 && num[2] > 0) {
                num[s.charAt(left++) - 'a']--;
            }
            cnt += left;
        }
        return cnt;
    }
}

🥦 关键原理说明(为什么 cnt += left 是对的)

循环结束后窗口 left, right 一定不满足三种字符齐全。

那么:

  • 起点 0,1,...,left-1,终点 right:子串 s0,right、s1,right...sleft-1,right
  • 这些子串都包含完整 a/b/c,总共有 left 个。
🥦 运行效率
  1. 时间复杂度 O (n):left 和 right 最多各自遍历字符串一次,无嵌套多层循环。
  2. 空间复杂度 O (1):仅固定长度 3的数组,额外空间不随字符串长度变化。
🥦 思考

求解方法:滑动窗口/双指针
核心考察:利用滑动窗口统计满足字符频次条件的子串数量。
思考:如果字符种类变成 k 种,你的代码需要怎么灵活调整呢?

💬 共勉

|----------------------------------|
| 最后,我想和大家分享一句一直激励我的座右铭,希望可以与大家共勉! |