青训营-小S的01问号串

1 阅读3分钟

问题描述

小S定义了一个字符串的权值,规则是:相邻的不同字符对的数量。例如,对于字符串 "10011",其权值为2,因为其中有2对相邻字符是不同的。现在小S拿到一个只包含 '0'、 '1' 和 '?' 的字符串,其中 '?' 表示这个位置可以是 '0' 或 '1'。已知共有2k2k种可能的字符串(kk 是问号的数量)。小S希望知道,这2k2k种可能的字符串的权值之和是多少?注意答案需要对109+7109+7取模。

例如,对于字符串 "01?",可能的串是 "010" 和 "011",它们的权值分别为2和1,权值和为3。


测试样例

样例1:

输入:s = "01?"
输出:3

样例2:

输入:s = "1??0"
输出:6

样例3:

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

问题分析

  1. 定义权值:字符串的权值定义为相邻不同字符对的数量,例如字符串"10011"中,有2对相邻字符是不同的。
  2. 未知字符:字符串中可能包含字符?,每个?可以是01。因此,含有k?的字符串有 2k2^k 种可能的替换。
  3. 目标:计算所有可能的字符串的权值之和,并对结果取模 109+710^9 + 7

解题思路

  1. 动态规划:我们可以通过递归或动态规划的方式来计算所有可能的字符串及其权值。

  2. 状态转移:对于每一个?字符,我们可以将其替换为01,分别计算替换后的权值,然后累加所有可能的组合。

  3. 记忆化:为了避免重复计算,可以使用记忆化存储每个子问题的结果。

  4. 边界情况

    • 如果字符串中没有?,直接计算权值。
    • 如果字符串长度为1,权值总是0,因为没有相邻字符。

示例解答

我们可以用递归 + 记忆化的方法实现。

MOD = 10**9 + 7

def calculate_weight(s):
    # 计算字符串的权值
    weight = 0
    for i in range(1, len(s)):
        if s[i] != s[i - 1]:
            weight += 1
    return weight

def dfs(s, index, memo):
    # DFS 搜索所有可能的字符串
    if index == len(s):
        # 如果到达字符串末尾,计算权值
        return calculate_weight(s)
    
    # 使用记忆化来存储已计算的状态
    if (s, index) in memo:
        return memo[(s, index)]
    
    if s[index] == '?':
        # 如果当前位置是问号,分别替换为 '0' 和 '1' 并计算总和
        sum_weight = 0
        for char in '01':
            new_s = s[:index] + char + s[index+1:]
            sum_weight += dfs(new_s, index + 1, memo)
            sum_weight %= MOD
        memo[(s, index)] = sum_weight
        return sum_weight
    else:
        # 如果当前位置不是问号,继续向下递归
        result = dfs(s, index + 1, memo)
        memo[(s, index)] = result
        return result

def solution(s: str) -> int:
    memo = {}
    return dfs(s, 0, memo)

# 测试用例
if __name__ == '__main__':
    print(solution("01?") == 3)   # True
    print(solution("1??0") == 6)  # True
    print(solution("?") == 0)     # True

解释代码

  1. calculate_weight: 用于计算没有?的字符串的权值。
  2. dfs: 使用递归和记忆化来遍历字符串的所有可能组合。如果遇到?,分别替换成01,并递归计算权值之和。
  3. solution: 主函数,初始化记忆化字典memo,并调用dfs进行计算。