返回

利用线性DP、树状数组与计数,探索环绕字符串中的独特子字符串

后端




LeetCode上题号467的环绕字符串中唯一子字符串问题,以中等难度挑战着程序员们的智慧。在本文中,我们将为你一一剖析如何运用线性动态规划、树状数组和同字符长度计数,共同解决这道颇具创意的题目。

问题解析

题目要求我们计算环绕字符串中唯一子字符串的数量。所谓环绕字符串,就是将字符串首尾相连,形成一个环。例如,字符串"abc"的环绕字符串有"abc", "bca", "cab"。子字符串则是字符串中的连续字符。例如,字符串"abc"的子字符串包括"a", "ab", "abc", "b", "bc", "c"

解题方案

环绕字符串中的唯一子字符串,是我们通过环绕这一特性,找到的那些在原字符串中并不存在的子字符串。因此,我们可以通过一系列巧妙的算法步骤,来计算出这些独一无二的子字符串的数量:

  1. 线性动态规划

    我们将字符串的每个字符作为状态,并计算从该字符开始的所有环绕子字符串的数量。状态转移方程如下:

    dp[i] = dp[i-1] + dp[i-2] + ... + dp[0]
    

    其中dp[i]表示以第i个字符开始的所有环绕子字符串的数量。

  2. 树状数组

    为了快速计算dp数组,我们可以使用树状数组来优化计算过程。树状数组是一种可以高效地进行区间查询和更新的数据结构。

  3. 同字符长度计数

    对于每个字符,我们还需要统计该字符在环绕字符串中出现的次数。我们可以使用一个数组来记录每个字符出现的次数。

算法实现

def count_unique_substrings(s):
  """
  计算环绕字符串中唯一子字符串的数量。

  参数:
    s: 输入字符串

  返回:
    环绕字符串中唯一子字符串的数量
  """

  # 创建状态数组
  dp = [0] * len(s)

  # 创建树状数组
  bit = BinaryIndexedTree(len(s))

  # 创建同字符长度计数数组
  char_count = [0] * 26

  # 初始化状态数组
  for i in range(len(s)):
    dp[i] = 1

    # 更新树状数组
    bit.update(i, 1)

    # 更新同字符长度计数数组
    char_count[ord(s[i]) - ord('a')] += 1

  # 计算环绕子字符串的数量
  for i in range(1, len(s)):
    for j in range(i):
      # 如果当前字符和上一个字符相同,则跳过
      if s[i] == s[j]:
        continue

      # 计算当前子字符串的长度
      length = i - j + 1

      # 计算当前子字符串在环绕字符串中出现的次数
      count = bit.query(i) - bit.query(j - 1)

      # 如果当前子字符串在环绕字符串中出现的次数为1,则它是唯一的
      if count == 1:
        # 计算当前子字符串中同字符的个数
        same_char_count = 0
        for k in range(26):
          if char_count[k] >= length:
            same_char_count += 1

        # 如果当前子字符串中没有同字符,则它是唯一的
        if same_char_count == 0:
          dp[i] += 1

  # 返回环绕字符串中唯一子字符串的数量
  return dp[len(s) - 1]


class BinaryIndexedTree:
  """
  树状数组
  """

  def __init__(self, n):
    self.tree = [0] * (n + 1)

  def update(self, i, delta):
    """
    更新树状数组中第i个元素的值

    参数:
      i: 要更新的元素的索引
      delta: 要增加的值
    """

    while i < len(self.tree):
      self.tree[i] += delta
      i += i & -i

  def query(self, i):
    """
    查询树状数组中从0到第i个元素的和

    参数:
      i: 要查询的元素的索引

    返回:
      树状数组中从0到第i个元素的和
    """

    sum = 0
    while i > 0:
      sum += self.tree[i]
      i -= i & -i

    return sum

结语

通过将线性动态规划、树状数组和同字符长度计数三种算法有机结合,我们成功地解决了环绕字符串中唯一子字符串的计数问题。这种综合应用不同算法的解题方式,体现了算法思想的灵活性和创造力。