问题描述
小Y有一个数字串,她希望通过分隔这个字符串来获得一些子串,每个子串代表一个数字。她的目标是最大化能获得的是 33 的倍数的数字的数量。分隔后的数字串不能包含前导零(但数字 00 本身是允许的),因为 00 也被视为 33 的倍数。
例如,对于数字串 1123
,可以将其分割为 [1, 12, 3]
,其中 12
和 3
是 33 的倍数,因此小Y最多可以获得 2 个是 33 的倍数的数字。
解题思路
- 理解3的倍数的特性
- 一个数是3的倍数,当且仅当它的各位数字之和是3的倍数。
- 例如,数字
12
是3的倍数,因为1 + 2 = 3
,而3是3的倍数。
- 分割策略
- 我们需要考虑如何分割数字串,使得每个子串的各位数字之和是3的倍数。
- 分割时要注意不能包含前导零,除非子串本身就是
0
。
- 动态规划或贪心算法
- 可以考虑使用动态规划来记录每个位置的最大3的倍数子串数量。
- 或者使用贪心算法,尝试从左到右分割,确保每次分割的子串是3的倍数。
- 边界情况
- 处理单个数字的情况,如
0
或3
。 - 处理整个数字串本身就是3的倍数的情况。
- 实现步骤
- 遍历数字串,计算每个位置的数字和。
- 尝试从每个位置分割,检查分割后的子串是否是3的倍数。
- 记录并更新最大数量的3的倍数子串。
代码实现
def solution(n: str) -> int:
dp = [0] * (len(n) + 1)
for i in range(1, len(n) + 1):
if int(n[i-1]) % 3 == 0:
dp[i] = dp[i-1] + 1
else:
dp[i] = dp[i-1]
current_sum = 0
for j in range(i, 0, -1):
current_sum += int(n[j-1])
if current_sum % 3 == 0:
dp[i] = max(dp[i], dp[j-1] + 1)
return dp[len(n)]
注意
- 检查前导零:在尝试分割子串时,检查是否有前导零。如果
n[j-1]
是 '0',并且j
不是i
(即不是当前字符),则跳过这个分割。 - 更新
dp
数组:只有在没有前导零的情况下,才更新dp
数组。
时间复杂度分析
当前代码的主要时间复杂度来自于两个嵌套的循环:
- 外层循环:遍历字符串
n
,时间复杂度为O(n)
,其中n
是字符串的长度。 - 内层循环:对于每个字符
i
,内层循环从i
遍历到1
,时间复杂度为O(i)
。
因此,内层循环的总时间复杂度为:
[ \sum_{i=1}^{n} i = \frac{n(n+1)}{2} = O(n^2) ]
所以,整个算法的时间复杂度为 O(n^2)
。
空间复杂度分析
当前代码的空间复杂度主要来自于动态规划数组 dp
,其长度为 n + 1
,因此空间复杂度为 O(n)
。
总结
- 时间复杂度:
O(n^2)
- 空间复杂度:
O(n)
优化
def solution(n: str) -> int:
dp = [0] * (len(n) + 1)
prefix_sum = [0] * (len(n) + 1)
for i in range(1, len(n) + 1):
prefix_sum[i] = prefix_sum[i-1] + int(n[i-1])
for i in range(1, len(n) + 1):
if int(n[i-1]) % 3 == 0:
dp[i] = dp[i-1] + 1
else:
dp[i] = dp[i-1]
for j in range(i, 0, -1):
current_sum = prefix_sum[i] - prefix_sum[j-1]
if current_sum % 3 == 0 and (j == i or n[j-1] != '0'):
dp[i] = max(dp[i], dp[j-1] + 1)
return dp[len(n)]
优化后的复杂度分析
- 时间复杂度:
O(n^2)
(虽然仍然是O(n^2)
,但减少了内层循环的计算量) - 空间复杂度:
O(n)
(增加了前缀和数组的空间开销)