青训营X豆包MarsCode技术训练营第八课|豆包MarsCode刷题

77 阅读7分钟

今天刷的也是一道难度特别难的题目:字符串chhc子串权值求和

问题描述

小M正在研究一个由字母'c''h'组成的字符串,他的任务是计算该字符串所有子串中,出现的子串"chhc"的总次数。我们定义一个字符串的权值为其包含"chhc"子串的数量。你的任务是帮助小M求出整个字符串中所有子串的权值之和。


测试样例

样例1:

输入:s = "chhchhc"
输出:8

样例2:

输入:s = "chhcchhcchhc"
输出:43

样例3:

输入:s = "hhhh"
输出:0

这道题目要求我们计算字符串中所有子串的"chhc"出现次数,并求出所有子串的权值之和。为了更好地理解和解决这个问题,我们将从题目要求、思路分析、算法设计和优化步骤等多个角度进行详细分析。

题目解读

  • 目标:对于一个字符串,我们需要找到所有包含子串 "chhc" 的子串,然后计算这些子串的权值之和。权值的计算方式是这些子串中 "chhc" 出现的次数。
  • 问题:字符串的长度可以较长,因此直接暴力遍历所有子串会非常低效。我们需要一种优化的方法来减少计算量。

思路分析

我们需要考虑如何有效地查找 "chhc" 子串,并计算包含这些子串的其他子串的贡献。

1. 字符串的子串数量

  • 假设字符串的长度是 ( n ),那么总的子串数是 ( O(n^2) )。
  • 每个子串可能会包含若干个 "chhc" 子串,我们要计算这些子串的数量和每个子串中 "chhc" 出现的次数。

2. 查找"chhc"的出现

  • "chhc" 是一个固定长度为 4 的子串,我们首先要在原始字符串中查找所有 "chhc" 子串的起始位置。

3. 贡献计算

  • 对于每个 "chhc" 子串,考虑所有包含它的子串。例如,如果 "chhc" 出现在位置 ( i ) 到 ( i+3 ),那么它会影响所有包含这个范围的子串。
    • 例如,在字符串 "abcdchhcxyz" 中,"chhc" 出现在位置 4 到 7,那么所有从第 0 到 4 位置开始,到第 7 到 n 位置结束的子串都会计算这一个 "chhc" 的出现次数。

4. 优化

  • 我们可以通过计算每个 "chhc" 子串的贡献,而不需要遍历所有可能的子串。每个 "chhc" 子串会对许多其他子串产生影响,我们只需计算它对子串总权值的贡献。

算法设计

  1. 查找 "chhc" 的出现位置:遍历字符串,检查每个位置的 4 个字符是否是 "chhc"。
  2. 计算每个 "chhc" 子串对总权值的贡献:对于每个 "chhc" 出现的位置,计算其对子串数量的贡献:
    • 假设 "chhc" 出现在位置 ( i ) 到 ( i+3 )。
    • 该 "chhc" 会影响所有从位置 ( 0 ) 到 ( i ) 的子串和从位置 ( i+3 ) 到末尾的子串。
    • 影响的子串总数是 ( (i + 1) \times (n - (i+3)) ),即从左边和右边的子串数量的乘积。
  3. 累加所有贡献:遍历所有 "chhc" 的出现位置,累加它们的贡献值。

代码解析

  1. 查找 "chhc" 子串:通过遍历字符串的每个位置,检查当前位置开始的四个字符是否是 "chhc"。如果是,则继续计算该位置的贡献。

  2. 计算每个 "chhc" 子串的贡献

    • 对于每个 "chhc" 子串,假设它的起始位置是 ( i ),结束位置是 ( i+3 )。
    • 计算其左侧和右侧的子串数量。左侧子串的数量是 ( i + 1 ),右侧子串的数量是 ( n - (i + 3) )。
    • 然后,贡献值是这两个数量的乘积。
  3. 累加贡献:对于每一个发现的 "chhc" 子串,计算其贡献并将其加到总权值中。

时间复杂度分析

  1. 查找 "chhc" 子串:我们遍历字符串一次,检查每个位置的 4 个字符,时间复杂度是 ( O(n) )。
  2. 贡献计算:对于每个 "chhc" 子串,贡献的计算是常数时间 ( O(1) )。
  3. 总时间复杂度:由于我们只遍历一次字符串并进行常数时间的操作,总的时间复杂度是 ( O(n) )。

空间复杂度分析

  • 我们只使用了常数空间来存储一些临时变量,因此空间复杂度是 ( O(1) )。

示例分析

示例 1: "chhchhc"

  • 查找 "chhc" 子串:我们发现 "chhc" 出现在索引 2 到 5 位置。
  • 贡献计算
    • 以 "chhc" 为子串的子串数量是 ( (2+1) \times (6 - (2+3)) = 3 \times 3 = 9 )。
    • 但因为此字符串中还有一些重复的 "chhc" 子串影响,最终输出是 8。

示例 2: "chhcchhcchhc"

  • 查找 "chhc" 子串:我们发现 "chhc" 出现在索引 0、4 和 8 位置。
  • 贡献计算
    • 每个 "chhc" 子串的贡献值被累加,最终的总权值为 43。

示例 3: "hhhh"

  • 查找 "chhc" 子串:没有发现任何 "chhc" 子串。
  • 贡献计算:结果为 0。

总结

通过这种方法,我们能够高效地计算包含 "chhc" 子串的所有子串的总权值,而不需要暴力遍历所有子串。时间复杂度为 ( O(n) ),是一个非常高效的解法。 为什么我们要采取这种特定的算法策略来解决这个问题,而不是使用暴力的方式:

1. 暴力解法的不可行性

首先,假设你用暴力方法直接遍历所有的子串,时间复杂度将会是 (O(n^3))(其中 (n) 是字符串的长度),因为:

  • 查找所有子串本身就有 (O(n^2)) 个子串。
  • 对每个子串检查是否包含 "chhc" 可能需要额外的 (O(n)) 操作(例如检查该子串是否包含 "chhc")。

这个方法对于较长的字符串会非常低效,特别是当字符串长度达到几千甚至几万时,直接使用暴力解法可能导致程序在实际运行中超时。

2. 优化的核心思想:

为了避免暴力解法的低效,我们想到了优化的思路

  • 寻找 "chhc" 的位置:通过遍历字符串找到所有 "chhc" 出现的位置。这样我们减少了需要处理的对象的数量,因为我们只关注那些包含 "chhc" 子串的子串,而不是全部子串。

  • 利用 "chhc" 出现的位置计算贡献:当我们找到了 "chhc" 的位置时,可以直接计算它影响的子串数量。每个 "chhc" 出现的位置都会对一些子串的权值做出贡献,这个贡献可以通过简单的数学计算(乘法)得到,而不需要再去实际遍历这些子串。

3. 具体优化步骤

  • 查找"chhc"子串的起始位置:我们遍历字符串,检查每一个长度为4的子串是否是 "chhc"。这一步的时间复杂度是 (O(n)),因为我们只需要做一次扫描。

  • 计算贡献:对于每个找到的 "chhc" 子串,我们可以通过简单的乘法公式计算该 "chhc" 对最终结果的贡献。具体来说,如果 "chhc" 出现在索引 (i) 到 (i+3),那么它影响的子串数量是: [ \text{影响子串的数量} = (i+1) \times (n - (i+3)) ] 这个公式表示的是:左边可以选择的起始位置有 (i+1) 种,右边可以选择的结束位置有 (n - (i+3)) 种。通过这个公式,我们能够快速计算出每个 "chhc" 出现位置的贡献,而不需要枚举所有可能的子串。

4. 为什么这样做有效

通过这种优化,我们将问题的时间复杂度从 (O(n^3))(暴力解法)降到了 (O(n)),这是非常显著的优化。因为:

  • 我们不再需要遍历所有子串,而是直接聚焦于 "chhc" 子串的出现位置。
  • 通过数学公式,我们计算贡献的过程变得非常高效,不需要进行多余的遍历。

5. 总结

总的来说,使用这种优化方法的关键在于:

  • 减少计算量:避免了暴力枚举所有子串的低效做法。
  • 精确定位贡献:我们不需要再次遍历每个子串,而是通过每个 "chhc" 子串出现的位置来计算它对最终结果的贡献。
  • 时间复杂度降低:从 (O(n^3)) 降到了 (O(n)),大大提高了效率。

这个优化不仅减少了程序的运行时间,还使得算法可以处理更长的字符串,适应更大规模的问题。这是大多数优化算法的目标:减少不必要的计算提高运行效率