一、《K排序最小操作次数计算》
问题理解
给定一个长度为 n 的排列 a,使用 K 排序算法可以通过以下步骤将其从小到大排序:
- 每次从数列中选择最多
K个位置的数,将它们移除后,将剩余的数左对齐。 - 接着对移除的数进行排序,并将它们放在数列的末尾。
需要计算最少需要多少次这样的操作,才能使数列按升序排列。
这道题虽然被分在困难里,但是难度还是比较低的。
数据结构选择
- 数组:输入的排列
a是一个数组,长度为n。 - 整数变量:用于计数和索引操作。
算法步骤
-
初始化计数器:
cnt用于记录已经在正确位置上的元素数量。j用于跟踪当前应该在正确位置上的元素值。
-
遍历数组:
- 遍历数组
a,检查每个元素是否在其正确的位置上(即a[i] == j)。 - 如果是,则增加
cnt和j。
- 遍历数组
-
计算剩余未排序的元素:
- 使用
n - cnt计算剩余未排序的元素数量。
- 使用
-
计算最少操作次数:
- 使用
math.ceil((n - cnt) / k)计算最少需要多少次操作才能将剩余的元素排序。 - 其中
(n - cnt) / k表示每次操作最多可以处理k个元素,math.ceil用于向上取整。
- 使用
-
返回结果:
- 返回计算得到的最少操作次数。
代码实现
import math
def solution(n: int, k: int, a: list) -> int:
cnt = 0
j = 1
for i in range(n):
if j == a[i]:
cnt += 1
j += 1
ans = math.ceil((n - cnt) / k)
return ans
二、《分割数字串获取3的倍数问题》
问题理解
小Y需要将一个由数字组成的字符串 S 分割成若干子串,每个子串代表一个数字。目标是最大化这些子串中是 3 的倍数的数量。分割后的子串不能包含前导零,除非子串本身是 "0"。
示例:
- 输入:
"1123" - 可能的分割:
[1, 12, 3],其中12和3是3的倍数,因此结果为2。
常见的动态规划问题,记录前缀和的方法之前见的少,多亏ai的帮助才能做的出来。
数据结构选择
- 动态规划(DP)数组:用来记录到字符串中每个位置为止,可以获得的最大
3的倍数子串数量。定义dp[i]表示字符串前i个字符(S[0]到S[i-1])所能获得的最大3的倍数子串数量。 - 前缀和数组:用来快速计算任意子串的数字和是否为
3的倍数。定义prefix_sum[i]为字符串前i个字符的数字之和。
算法步骤
-
初始化:
-
计算字符串的长度
n。 -
创建前缀和数组
prefix_sum,其中prefix_sum[0] = 0。 -
prefix_sum[i]表示字符串S前i个字符的数字之和。通过累加每个字符对应的整数值来计算。 -
初始化 DP 数组
dp,长度为n + 1,所有值设为-∞,表示初始不可达。设定dp[0] = 0。 -
dp[i]表示前i个字符可以分割成最多多少个3的倍数的子串。dp[0] = 0表示空字符串有0个3的倍数子串。
-
-
动态规划填表:
-
使用一个字典
last_mod来记录每个prefix_sum[i] % 3最后一次出现的位置。这样可以快速找到符合条件的子串。 -
初始化时,
last_mod = {0: 0, 1: -1, 2: -1},表示在位置0时,prefix_sum % 3 = 0。 -
遍历字符串的每个位置
i:- 计算当前前缀和模
3的值current_mod = prefix_sum[i] % 3。 - 如果
current_mod在last_mod中存在位置j,且子串S[j:i]不含前导零(除非子串为"0"),则更新dp[i] = max(dp[i], dp[j] + 1)。 - 同时,保留之前的最大值
dp[i - 1],以确保dp[i]不会因不分割而减小。 - 更新
last_mod[current_mod] = i。
- 计算当前前缀和模
-
-
处理无匹配的情况:
- 如果某个位置无法通过前面的子串分割来形成
3的倍数,则保持dp[i]不变。
- 如果某个位置无法通过前面的子串分割来形成
-
返回结果:
- 最终答案为
dp[n],即整个字符串分割后的最大3的倍数子串数量。
- 最终答案为
代码实现
def solution(S: str) -> int:
n = len(S) # 字符串的长度
dp = [float('-inf')] * (n + 1) # 初始化DP数组,长度为n+1,初始值为负无穷
dp[0] = 0 # 空字符串有0个3的倍数子串
prefix_sum = [0] * (n + 1) # 初始化前缀和数组,长度为n+1
for i in range(1, n + 1):
prefix_sum[i] = prefix_sum[i - 1] + int(S[i - 1]) # 计算前缀和
last_mod = {0: 0, 1: -1, 2: -1} # 记录每个mod3值最后出现的位置
for i in range(1, n + 1):
current_mod = prefix_sum[i] % 3 # 当前前缀和模3的值
if last_mod.get(current_mod, -1) != -1:
j = last_mod[current_mod]
# 检查子串是否有前导零
if S[j] != '0' or (j + 1 == i):
if dp[j] + 1 > dp[i]:
dp[i] = dp[j] + 1 # 更新dp[i],表示前i个字符可以获得的最大3的倍数子串数量
# 保持之前的最大值
if dp[i - 1] > dp[i]:
dp[i] = dp[i - 1]
# 更新last_mod
last_mod[current_mod] = i
return dp[n] # 返回整个字符串分割后的最大3的倍数子串数量