AI 刷题 | 豆包MarsCode AI刷题

83 阅读3分钟

问题描述

小G定义了一个字符串的权值为:字符串的长度乘以字符串中不同字母的种类数量。例如,对于字符串 "abacb",其权值为 5 × 3 = 15,因为字符串的长度为 5,且包含 3 种不同的字母。

现在,小G有一个字符串,她希望将这个字符串切分成 k 个子串,并希望这些子串的最大权值尽可能小。你需要帮助小G找到最优的切分方案,使得这 k 个子串中的最大权值最小化。

测试样例 样例1:

输入:s = "ababbbb" ,k = 2 输出:6

样例2:

输入:s = "abcabcabc" ,k = 3 输出:9

样例3:

输入:s = "aaabbbcccddd" ,k = 4 输出:3

问题本质

1. 本质是一个优化问题

我们需要找到一个切分方案,使得切分出的 k 个子串中 最大权值 最小化。对于某一子串,其权值由以下公式计算:

权值=len(s)×unique(s)

其中:

len(s):子串的长度。

unique(s):子串中不同字母的种类数量。

这就要求我们在切分时,同时考虑子串长度和其字母分布的影响。

2. 结合二分查找与贪心思想

由于子串的权值与切分方式相关,我们可以通过以下步骤求解:

二分查找最大权值:假设子串的最大权值为 mid,验证是否能通过 k 次切分满足条件。

贪心验证切分可行性:尝试从头开始依次划分子串,确保每段的权值不超过 mid。

解题思路

1. 二分查找最大权值

最大权值的范围可以通过以下方式确定:

最小值:如果不进行切分,即权值为整个字符串的权值。

最大值:每个字符单独切分时,权值等于字符数(即 n×1=n)。

通过二分法,我们可以快速确定一个可行的最大权值。

2. 贪心切分验证

每次尝试切分时,计算当前子串的权值:

如果子串的权值超过 mid,则将该子串结束,并开始新的子串。

如果切分次数 >k,说明当前的 mid 不可行。

代码实现

def calculate_weight(substring):
    """计算字符串的权值"""
    length = len(substring)
    unique_count = len(set(substring))
    return length * unique_count

def is_valid_cut(s, k, max_weight):
    """判断是否可以通过 k 次切分使得每段的权值 <= max_weight"""
    count = 1
    current_substring = ""
    for char in s:
        current_substring += char
        if calculate_weight(current_substring) > max_weight:
            count += 1
            current_substring = char  # 开始新子串
            if count > k:  # 切分次数超过 k
                return False
    return True

def minimize_max_weight(s, k):
    """使用二分查找最小化最大权值"""
    left = calculate_weight(s)  # 整个字符串作为一个子串的权值
    right = len(s) * len(set(s))  # 每个字符独立切分的最大权值
    
    while left < right:
        mid = (left + right) // 2
        if is_valid_cut(s, k, mid):
            right = mid  # 尝试更小的权值
        else:
            left = mid + 1  # 增加允许的最大权值
    return left

# 测试用例
if __name__ == "__main__":
    print(minimize_max_weight("ababbbb", 2))  # 输出: 6
    print(minimize_max_weight("abcabcabc", 3))  # 输出: 9
    print(minimize_max_weight("aaabbbcccddd", 4))  # 输出: 3

解题详解

1. 核心方法解释

(1) 权值计算

calculate_weight 函数计算任意子串的权值。通过 Python 内置的 set() 函数,我们快速获得子串中的不同字母数量。

(2) 切分验证

is_valid_cut 函数采用贪心思路,从头开始构造子串:

如果当前子串的权值超过 mid,则结束当前子串并开始新子串。 如果需要的子串数量超过 k,则返回 False。

(3) 二分查找

通过二分法缩小最大权值范围,直到找到最优解。

2. 时间复杂度分析

权值计算:每次调用 calculate_weight 的复杂度为 O(n),但实际复杂度会因子串长度较短而减少。

切分验证:对于长度为 n 的字符串,每次遍历复杂度为 O(n)。

二分查找:二分的次数为 O(log(max_weight))。

综合复杂度为 O(n×log(max_weight))。