《K排序最小操作次数计算》、《分割数字串获取3的倍数问题》 | 豆包MarsCode AI刷题

105 阅读5分钟

一、《K排序最小操作次数计算》

问题理解

给定一个长度为 n 的排列 a,使用 K 排序算法可以通过以下步骤将其从小到大排序:

  1. 每次从数列中选择最多 K 个位置的数,将它们移除后,将剩余的数左对齐。
  2. 接着对移除的数进行排序,并将它们放在数列的末尾。

需要计算最少需要多少次这样的操作,才能使数列按升序排列。

这道题虽然被分在困难里,但是难度还是比较低的。

数据结构选择

  • 数组:输入的排列 a 是一个数组,长度为 n
  • 整数变量:用于计数和索引操作。

算法步骤

  1. 初始化计数器

    • cnt 用于记录已经在正确位置上的元素数量。
    • j 用于跟踪当前应该在正确位置上的元素值。
  2. 遍历数组

    • 遍历数组 a,检查每个元素是否在其正确的位置上(即 a[i] == j)。
    • 如果是,则增加 cnt 和 j
  3. 计算剩余未排序的元素

    • 使用 n - cnt 计算剩余未排序的元素数量。
  4. 计算最少操作次数

    • 使用 math.ceil((n - cnt) / k)计算最少需要多少次操作才能将剩余的元素排序。
    • 其中 (n - cnt) / k 表示每次操作最多可以处理 k 个元素,math.ceil 用于向上取整。
  5. 返回结果

    • 返回计算得到的最少操作次数。

代码实现

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 个字符的数字之和。

算法步骤

  1. 初始化

    • 计算字符串的长度 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 的倍数子串。

  2. 动态规划填表

    • 使用一个字典 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. 处理无匹配的情况

    • 如果某个位置无法通过前面的子串分割来形成 3 的倍数,则保持 dp[i] 不变。
  4. 返回结果

    • 最终答案为 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 = {001: -12: -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的倍数子串数量