题目解析 Day 3 | 豆包MarsCode AI刷题

501 阅读30分钟

1. 英雄升级与奖励最大化

问题描述

在一个游戏中,小W拥有 n 个英雄,每个英雄的初始能力值均为 1。她可以通过升级操作来提升英雄的能力值,最多可以进行 k 次升级。

每次升级操作包含以下步骤:

  1. 选择一个英雄

  2. 选择一个正整数 x

  3. 将该英雄的能力值 aia_{i}  更新为:ai=ai+ai/xa_{i} =a_{i} +⌊a_{i} /x⌋

    • 其中 ⌊ ⌋ 表示向下取整操作

游戏规则:

  • 当英雄的能力值首次达到或超过目标值 bi b_{i} 时,小W可以获得对应的奖励 cic_{i}
  • 每个英雄的奖励只能获得一次
  • 升级操作的选择是自由的,可以多次选择同一个英雄进行升级

请计算在最多进行 k 次升级操作后,小W能获得的最大奖励总和。
输入:n = 4 ,k = 4 ,b = [1, 7, 5, 2] ,c = [2, 6, 5, 2]
输出:9
解释:可以通过以下操作获得最大奖励:

  1. 第一个英雄初始值为 1,已达到目标值 1,直接获得奖励2
  2. 第四个英雄初始值为 1,选择 x=1 升级一次变为 2,达到目标值2,获得奖励2
  3. 第三个英雄通过三次合理的升级操作 (1->2->4->5) 可达到目标值5,获得奖励5
    总奖励为:2 + 2 + 5 = 9

解题思路

这是一个最优解问题,我们考虑使用动态规划算法来求解问题。首先我们设计一个二维dp数组,其中dp[i][j]表示前i个英雄在第j次升级操作后能获得的最大奖励。对于英雄i的第j次升级,我们假设有upgrade_count次属于该英雄,那么前i - 1个英雄就有j - upgrade_count次升级。当第i个英雄经过upgrade_count次升级后大于等于目标值,于是就有状态转移方程dp[i][j] = max(dp[i][j], dp[i - 1][j - upgrade_count] + c[i - 1])

代码实现

def solution(n, k, b, c):
    # 初始化一个二维数组 dp,dp[i][j] 表示前 i 个英雄在 j 次升级操作后能获得的最大奖励
    dp = [[0] * (k + 1) for _ in range(n + 1)]

    # 遍历每个英雄
    for i in range(1, n + 1):
        # 遍历每次升级操作的次数
        for j in range(k + 1):
            # 初始状态:不升级当前英雄
            dp[i][j] = dp[i - 1][j]
        
            # 尝试升级当前英雄
            for upgrade_count in range(0, j + 1):
                # 计算升级后的能力值,这里每次选择 x = 1 使得升级最大化
                new_ability = 2 ** upgrade_count  
                
                # 如果升级后的能力值达到或超过目标值
                if new_ability >= b[i - 1]:
                    # 更新 dp 数组
                    dp[i][j] = max(dp[i][j], dp[i - 1][j - upgrade_count] + c[i - 1])
    
    # 返回最大奖励
    return dp[n][k]

2. 时尚圈的衣着稳定性问题

问题描述

小U在时尚圈组织了一场“校服日”活动,有 n 名学生围成一圈参加活动。每位学生都穿着白色或黑色的校服,白色用 0 表示,黑色用 1 表示。每一天,如果某个学生发现自己和相邻的两位学生穿着相同颜色的校服,那么他会在第二天更换成另一种颜色的校服;否则,他会保持不变。

你的任务是帮助小U判断,在第几天学生们的穿着会达到稳定状态——即某一天后,所有学生的穿着不再发生任何变化。同时,你还需要计算在稳定后,有多少学生的校服颜色与其相邻的同学不同,这些学生被称为“时尚达人”。

最终你需要返回一个包含两个元素的数组:

  • 第一个数字表示稳定下来的天数。如果永远无法稳定,则输出 -1
  • 第二个数字表示稳定后成为“时尚达人”的学生人数。如果永远无法稳定,则输出 -1

解题思路

通过分析发现,只有当数组全0或全1时,这种状态下永远无法稳定,我们直接返回。对于其余情况,我们通过模拟每一天的变化,对于每个学生,检查他和他相邻的两个学生的校服颜色。如果某个学生发现自己和相邻的两位学生穿着相同颜色的校服,那么他会在第二天更换成另一种颜色的校服;否则,他会保持不变。如果某一天后,所有学生的校服颜色不再发生变化,则达到稳定状态。

代码实现

def solution(n, data):
    # 初始检查:如果初始状态全为0或全为1,直接返回[-1, -1]
    if all(x == '0' for x in data) or all(x == '1' for x in data):
        return [-1, -1]

    # 初始化当前状态
    current_state = list(data)
    days = 0

    while True:
        next_state = current_state[:]
        changed = False

        for i in range(n):
            left = (i - 1) % n
            right = (i + 1) % n

            # 检查当前学生和相邻学生的校服颜色
            if current_state[i] == current_state[left] == current_state[right]:
                # 如果三个学生颜色相同,则当前学生在第二天更换颜色
                next_state[i] = '1' if current_state[i] == '0' else '0'
                changed = True
            else:
                # 否则保持不变
                next_state[i] = current_state[i]

        # 如果某一天没有任何变化,则达到稳定状态
        if not changed:
            break

        # 更新当前状态为下一天的状态
        current_state = next_state
        days += 1

    # 计算稳定后的时尚达人数量
    fashion_count = 0
    for i in range(n):
        left = (i - 1) % n
        right = (i + 1) % n
        if current_state[i] != current_state[left] and current_state[i] != current_state[right]:
            fashion_count += 1

    return [days + 1, fashion_count]

3. 最大乘积区间问题

问题描述

小R手上有一个长度为 n 的数组 (n > 0),数组中的元素分别来自集合 [0, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]。小R想从这个数组中选取一段连续的区间,得到可能的最大乘积。

你需要帮助小R找到最大乘积的区间,并输出这个区间的起始位置 x 和结束位置 y (x ≤ y)。如果存在多个区间乘积相同的情况,优先选择 x 更小的区间;如果 x 相同,选择 y 更小的区间。

注意:数组的起始位置为 1,结束位置为 n

解题思路

由于数组中的元素都是正数或零,乘积的结果可能会非常大。要注意零值会使得乘积变为零,因此我们需要特别处理包含零的区间。我们可以通过遍历数组来计算每个可能区间的乘积,并记录最大乘积及其对应的区间。如果存在多个区间乘积相同的情况,优先选择 x 更小的区间;如果 x 相同,选择 y 更小的区间。

代码实现

def solution(n, data):
    max_product = 0
    best_start = -1
    best_end = -1
    
    # 遍历数组,计算每个可能区间的乘积
    for i in range(n):
        current_product = 1
        for j in range(i, n):
            current_product *= data[j]
            
            # 如果当前乘积大于最大乘积,更新最大乘积和区间
            if current_product > max_product:
                max_product = current_product
                best_start = i + 1  # 题目要求起始位置为1
                best_end = j + 1    # 题目要求结束位置为1
            # 如果乘积相同,优先选择更小的区间
            elif current_product == max_product:
                if i + 1 < best_start or (i + 1 == best_start and j + 1 < best_end):
                    best_start = i + 1
                    best_end = j + 1
    
    return [best_start, best_end]
    
def solution(n, data): # 解法二
    # 初始化变量
    begin = [0] * n
    product = [1] * n
    
    # 初始化第一个元素
    product[0] = data[0]
    begin[0] = 0
    
    # 遍历数组,计算每个可能区间的乘积 其中product[i]表示以第i个元素向左的连续区间最大乘积
    for i in range(1, n):
        temp = product[i - 1] * data[i]
        if temp >= data[i]: # 取等号是为了保证多个区间乘积相同的情况,优先选择 x 更小的区间
            product[i] = temp
            begin[i] = begin[i - 1]
        else:
            product[i] = data[i]
            begin[i] = i
    
    # 找到最大乘积的索引
    ans = 0
    for i in range(1, n):
        if product[i] > product[ans]:
            ans = i
    
    # 返回结果,注意Python数组是0索引,而题目要求的是1索引,所以需要+1
    return [begin[ans] + 1, ans + 1]

4. 统计班级中的说谎者

问题描述

在小C的班级里,有 N 个学生,每个学生的成绩是 A_i。小C发现了一件有趣的事:当且仅当某个学生的成绩小于或等于自己的有更多人时,这个学生会说谎。换句话说,如果分数小于等于他的学生数量大于比他分数高的学生数量,则他会说谎。

现在,小C想知道班里有多少个学生会说谎。

解题思路

我们首先使用字典 score_count 来统计每个分数的人数。然后将得到的字典按分数进行排序,得到排序后的分数列表 sorted_scores。为了得到小于等于每个分数的人数,我们遍历排序后的分数列表,计算每个分数的前缀和 prefix_sum。最后,我们遍历每个学生的成绩,使用前缀和来判断该学生是否说谎。

代码实现

def solution(A):
    # 统计每个分数的人数
    score_count = {}
    for score in A:
        if score in score_count:
            score_count[score] += 1
        else:
            score_count[score] = 1
    
    # 对字典进行排序,得到排序后的分数列表
    sorted_scores = sorted(score_count.keys())
    
    # 计算前缀和
    prefix_sum = {}
    current_sum = 0
    for score in sorted_scores:
        current_sum += score_count[score]
        prefix_sum[score] = current_sum
    
    # 初始化说谎者的数量
    liar_count = 0
    
    # 遍历每个学生的成绩,判断是否说谎
    for score in A:
        # 获取当前分数的前缀和
        rank = prefix_sum[score]
        
        # 计算比当前学生成绩高的学生数量
        higher_count = len(A) - rank
        
        # 如果排名大于比当前学生成绩高的学生数量,则该学生说谎
        if rank > higher_count:
            liar_count += 1
    
    return liar_count

5. 兔群繁殖之谜

问题描述

生物学家小 R 正在研究一种特殊的兔子品种的繁殖模式。这种兔子的繁殖遵循以下规律:

  1. 每对成年兔子每个月会生育一对新的小兔子(一雌一雄)。
  2. 新生的小兔子需要一个月成长,到第二个月才能开始繁殖。
  3. 兔子永远不会死亡。

小 R 从一对新生的小兔子开始观察。他想知道在第 A 个月末,总共会有多少对兔子。

请你帮助小 R 编写一个程序,计算在给定的月份 A 时,兔子群体的总对数。

注意:

  • 初始时有 1 对新生小兔子。
  • 第 1 个月末有 1 对兔子:原来那对变成了成年兔子,并开始繁殖。
  • 第 2 个月末有 2 对兔子:原来那 1 对成年兔子,繁殖了 1 对新生的小兔子。
  • 从第 3 个月开始,兔子群体会按照上述规律增长。

解题思路

这是一道斐波那契数列问题,我们可以采用递归方法来求解,但时间复杂度太高。这里我采用双指针的做法,迭代的求出斐波那契数列的每一项,时间复杂度是O(n),空间复杂度是O(1)

代码实现

def solution(A):
    # 如果 A 小于等于 2 直接返回
    if A <= 2:  
        return A  
  
    n1 = 1  
    n2 = 2  
    temp = 0  
      
    for i in range(3, A + 1):  
        temp = n1 + n2  # 计算当前月的兔子对数
        n1 = n2  # 更新前一个月的兔子对数
        n2 = temp  # 更新当前月的兔子对数
      
    return n2  # 返回第 A 个月的兔子对数

6. 完美整数

问题描述

一个整数如果由相同的数字构成,则称为完美整数。例如:

  • 111333 是完美整数。
  • 1219101 是不完美整数。

现在,你需要计算给定区间 [x, y] 中有多少个整数是完美整数。

解题思路

这里我们可以遍历xy的每个数字判断其是否是完美整数,时间复杂度为O((y-x)d),其中d为区间里每个数字的平均位数。我们还可以使用逆向思维,用19生成完美数字,然后再判断是否在区间内。

代码实现

def solution(x, y):
    # 初始化计数器
    perfect_count = 0
    
    # 生成可能的完美整数的初始数字
    digits = [1, 2, 3, 4, 5, 6, 7, 8, 9]
    
    # 遍历初始数字数组
    for digit in digits:
        current = digit
        # 生成完美整数并检查是否在区间内
        while current < x:
            current = current * 10 + digit
        while current >= x and current <= y:
            perfect_count += 1
            current = current * 10 + digit
    
    # 返回完美整数的数量
    return perfect_count

7. 数字字符串格式化

问题描述

小M在工作时遇到了一个问题,他需要将用户输入的不带千分位逗号的数字字符串转换为带千分位逗号的格式,并且保留小数部分。小M还发现,有时候输入的数字字符串前面会有无用的 0,这些也需要精简掉。请你帮助小M编写程序,完成这个任务。

解题思路

我们首先使用 lstrip('0') 去除字符串前面的零。如果去除后字符串为空,则将其设置为 '0'。然后使用 split('.') 将字符串分为整数部分和小数部分。如果没有小数点,则整数部分为原字符串,小数部分为空。接着我们对整数部分添加千分位逗号,通过反转整数部分,每三位添加一个逗号,然后再反转回来。最后我们合并整数部分和小数部分,如果存在小数部分,则将其与整数部分合并,否则直接返回整数部分。

代码实现

def solution(s: str) -> str:
    # 去除前导零
    s = s.lstrip('0')
    if s == '':
        s = '0'
    
    # 分离整数部分和小数部分
    if '.' in s:
        integer_part, fractional_part = s.split('.')
    else:
        integer_part, fractional_part = s, ''
    
    # 对整数部分添加千分位逗号
    integer_part_with_commas = ''
    for i, char in enumerate(reversed(integer_part)):
        if i > 0 and i % 3 == 0:
            integer_part_with_commas = ',' + integer_part_with_commas
        integer_part_with_commas = char + integer_part_with_commas
    
    # 合并整数部分和小数部分
    if fractional_part:
        return integer_part_with_commas + '.' + fractional_part
    else:
        return integer_part_with_commas

8. 最佳人选

问题描述

某特种部队采用了一套性格密码机制来筛选执行特定任务的最佳士兵,该机制的规则如下:

  1. 每个人的性格可以通过 M 个维度来描述,每个维度分为 A, B, C, D, E 五种类型。
  2. 同一维度内,字母距离越近,性格类型差异越小,匹配程度越高。比如,A 和 B 的差异为 1,A 和 D 的差异为 3
  3. 其中 AEBDCEBE 为不相容性格类型,差异值设为无穷大(无法匹配)。
  4. 如果某一维度存在不相容性格类型,则表示两个士兵性格完全不匹配。
  5. 对于符合匹配条件的士兵,差异值总和越小表示匹配程度越高。

现在,有一个重要的机密任务,要求找到最匹配该任务所需性格密码的士兵。你需要编写一个算法,帮助部队找到符合条件的最佳人选。

  • m 表示性格密码的维度。
  • n 表示备选特种兵的数量。
  • target 是代表任务的性格密码。
  • array 是一个包含 n 个元素的列表,每个元素为 M 位的性格密码。

解题思路

我们首先使用一个map来记录每个字符之间的距离,如果为不相容性格类型,则将差异值设为无穷大。 然后我们遍历array数组,计算每个士兵的性格密码与目标性格密码的差异值,并存储在一个数组中。使用min函数找到差异数组中的最小值,如果为无穷大,则说明没有匹配的。最后我们将多个具有最小差异值的士兵性格密码用空格拼接成一个字符串返回。

代码实现

def solution(m, n, target, array):
    # 定义性格类型之间的差异
    diff_map = {
        'A': {'A': 0, 'B': 1, 'C': 2, 'D': 3, 'E': float('inf')},
        'B': {'A': 1, 'B': 0, 'C': 1, 'D': float('inf'), 'E': float('inf')},
        'C': {'A': 2, 'B': 1, 'C': 0, 'D': 1, 'E': float('inf')},
        'D': {'A': 3, 'B': float('inf'), 'C': 1, 'D': 0, 'E': 1},
        'E': {'A': float('inf'), 'B': float('inf'), 'C': float('inf'), 'D': 1, 'E': 0},
    }
    
    def calculate_diff(personality1, personality2):
        total_diff = 0
        for p1, p2 in zip(personality1, personality2):
            total_diff += diff_map[p1][p2]
        return total_diff
    
    # 计算每个人的差异值并存储在数组中
    diffs = []
    for personality in array:
        diffs.append(calculate_diff(target, personality))
    
    # 找到最小差异值
    min_diff = min(diffs)
    
    # 如果最小差异值是无穷大,返回 'None'
    if min_diff == float('inf'):
        return 'None'
    
    # 找到所有最小差异值的士兵,并用空格拼接
    best_matches = [array[i] for i in range(n) if diffs[i] == min_diff]
    return ' '.join(best_matches)

9. 小M的多任务下载器挑战

问题描述

小M的程序设计大作业是编写一个多任务下载器。在实现过程中,他遇到了一个问题:在一次下载过程中,总共有N个任务,每个任务会在第x秒开始,并持续y秒。小M需要知道,在同一时刻,最多有多少个任务正在同时下载,也就是计算出任务的最高并发数。

  • n 表示任务的数量。
  • array 是一个二维列表,每个元素为[x, y],表示任务的开始时间和持续时间,其中:
  • x 表示任务的开始时间;
  • y 表示任务的持续时间。

解题思路

我们可以使用一种称为“扫描线”或“事件驱动”的方法。这个方法的基本思路是将每个任务看作是两个事件:一个开始事件和一个结束事件。将任务转化为事件后,对所有事件按时间顺序排序。最后,我们遍历排序后的事件,维护一个计数器来记录当前正在进行的任务数,并更新最大并发数。

代码实现

def solution(n, array):
    # 创建一个列表来存储所有事件
    events = []
    
    # 将每个任务的开始和结束时间转换为事件
    for task in array:
        start_time = task[0]
        end_time = task[0] + task[1]
        # 开始事件:时间戳为开始时间,类型为1(表示任务开始)
        events.append((start_time, 1))
        # 结束事件:时间戳为结束时间,类型为-1(表示任务结束)
        events.append((end_time, -1))
    
    # 按时间对事件进行排序,如果时间相同,先处理结束事件(类型为-1)
    events.sort()
    
    # 初始化当前并发数和最大并发数
    current_concurrency = 0
    max_concurrency = 0
    
    # 遍历所有事件
    for event in events:
        # 根据事件类型更新当前并发数
        current_concurrency += event[1]
        # 更新最大并发数
        max_concurrency = max(max_concurrency, current_concurrency)
    
    return max_concurrency

10. 小M的星球时代冒险

问题描述

小M最近沉迷于一款叫《孢子》的游戏,游戏中,小M需要在一个星球上完成跑腿任务,提升自己。星球的地图可以抽象为一个nm列的矩阵,小M每次可以往上下左右任意方向移动一格。若小M往地图边缘移动,他会出现在另一边。例如,小M在第m列时往右移动会出现在第1列;在第1行时往上移动会出现在第n行。
这个星球的地图由一个生成器生成,分为两种地形。地图的生成规则如下:

  • 生成两个数列a[1...n]b[1...m],其中的值为01
  • 对于坐标(i,j)的点,若a[i] == b[j],则该点为地形A,否则为地形B。

小M需要从一个起点(x1, y1)移动到终点(x2, y2)执行任务。小M想知道他最少需要跨越多少次地形来完成任务。

  • n 表示地图的行数。
  • m 表示地图的列数。
  • a 是长度为n的列表,表示每一行的地形类型(值为01)。
  • b 是长度为m的列表,表示每一列的地形类型(值为01)。
  • q 表示任务的数量。
  • array 是一个二维列表,每个元素为[x1, y1, x2, y2],表示任务的起点和终点坐标。

解题思路

首先,我们可以根据数组 ab 的定义,可以生成一个 n x m 的地形矩阵 terrain[i][j],当 a[i] == b[j] 时设为 0(地形A),否则设为 1(地形B)。然后,对于每个任务 (x1, y1, x2, y2),使用 BFS 进行最短路径搜索。定义一个队列存储当前访问的坐标、地形切换次数和地形状态。因为要计算最少的地形跨越次数,可以记录已访问的坐标与切换次数,以防止重复访问。在 BFS 搜索中,处理四个方向(上、下、左、右)的移动,并根据越界情况进行环绕转换。当移动到新坐标时,检查新旧坐标地形是否不同,若不同则增加跨越次数。在找到 (x2, y2) 时的跨越次数作为该任务的最小地形切换次数。或者使用动态规划算法,其中dp[i][j]表示从起点出发所需的最少跨越地形次数。还可以考虑深度优先遍历

代码实现

def solution(n, m, a, b, q, array):
    # 生成地形地图
    terrain = [[0 if a[i] == b[j] else 1 for j in range(m)] for i in range(n)]

    results = []
    directions = [(-1, 0), (1, 0), (0, -1), (0, 1)]  # 上下左右

    for x1, y1, x2, y2 in array:
        x1, y1, x2, y2 = x1 - 1, y1 - 1, x2 - 1, y2 - 1
        if (x1, y1) == (x2, y2):
            results.append(0)
            continue

        # BFS 队列初始化,存储 (x, y, 跨越次数)
        queue = deque([(x1, y1, 0)])
        visited = set((x1, y1))  # 记录访问过的坐标

        found = False
        while queue and not found:
            cx, cy, changes = queue.popleft()

            for dx, dy in directions:
                nx, ny = (cx + dx) % n, (cy + dy) % m
                nx = nx + n if nx < 0 else nx
                ny = ny + m if ny < 0 else ny

                if (nx, ny) == (x2, y2):
                    # 如果到达终点,计算最终跨越次数
                    if terrain[cx][cy] != terrain[nx][ny]:
                        results.append(changes + 1)
                    else:
                        results.append(changes)
                    found = True
                    break

                if (nx, ny) not in visited:
                    visited.add((nx, ny))
                    # 判断是否跨越不同地形
                    if terrain[cx][cy] != terrain[nx][ny]:
                        queue.append((nx, ny, changes + 1))
                    else:
                        queue.append((nx, ny, changes))

    return results

11. 小M的弹子游戏机挑战

问题描述

小M最近迷上了一款弹子游戏机,规则如下:
玩家可以在版面最上方任意一个位置放置弹珠。弹珠会通过得分点时为玩家赢得分数,目标是获得尽可能高的分数。
弹子游戏机的版面由两种组成要素构成:

  1. 钉子(用 -1 表示),当弹珠碰到钉子时,有可能弹射到左下或者右下的位置。
  2. 得分点(非负整数),弹珠经过得分点时可以获得对应的分数。

如果弹珠所在的格子为空(即没有钉子或者得分点),弹珠会直接往下落。

小M想知道,在一个给定的版面布局中,他能够获得的最高分数是多少。

  • n 表示版面的高度。
  • m 表示版面的宽度。
  • array 是一个 n x m 的二维数组,其中:
  • -1 表示该位置为钉子;
  • 0 表示该位置为空;
  • 正整数表示该位置为得分点,值为该得分点的分数。

解题思路

我们可以使用递归或DFS遍历,从顶部每一列放入弹珠,从而探测所有可能路径上的得分。对于每个起始位置,递归探测弹珠路径并累积分数。为了处理钉子碰撞的弹射,我们会分别尝试弹射到左下或右下,直到弹珠出界或无法继续下落。或者使用动态规划来解决问题,可以定义一个二维数组 dp[i][j] 来表示从顶部某列位置 j 放入弹珠,在落到位置 (i, j) 处时所能获得的最大分数。每一层可以根据其上方的可能位置(上、左上、右上)来更新得分,这里要注意有些位置不可达,要做特殊处理。

代码实现

def solution(n, m, array):
    # 用于记录全局最高分数
    max_score = 0

    # 深度优先搜索函数,追踪当前坐标和累计得分
    def dfs(x, y, score):
        nonlocal max_score

        # 如果出界,直接返回
        if x >= n or y < 0 or y >= m:
            return

        # 更新得分
        if array[x][y] > 0:
            score += array[x][y]

        # 如果达到底部行或钉子后的下一层,则比较并更新最大分数
        if x == n - 1 or (array[x][y] == -1 and (x + 1 >= n or (y - 1 < 0 and y + 1 >= m))):
            max_score = max(max_score, score)
            return

        # 如果遇到钉子,尝试向左下和右下弹射
        if array[x][y] == -1:
            dfs(x + 1, y - 1, score)  # 左下弹射
            dfs(x + 1, y + 1, score)  # 右下弹射
        else:
            # 否则,直接向下移动
            dfs(x + 1, y, score)

    # 从每一列顶部发射弹珠,计算最大分数
    for j in range(m):
        dfs(0, j, 0)

    return max_score

12. 小M的能力选择挑战

问题描述

小M最近在玩一款叫做“狼人拯救者”的游戏。在游戏中,玩家可以通过消耗金币来购买能力,这些能力会增加攻击力,但也会影响攻击速度。
小M是一个以攻击力为优先的玩家,但他必须保证自己的攻击速度不能低于0,因为攻击速度为负时将无法攻击。
现在小M面对商店中的N种能力,他拥有G枚金币和S点初始攻击速度。他想知道,在保持攻击速度大于等于0的前提下,他最多可以获得多少攻击力。

商店中每种能力用三元组表示为 array[i] = [c, s, d],其中:

  • c 表示购买该能力需要的金币数;
  • s 表示该能力对攻击速度的影响,可以为正数、零或负数;
  • d 表示该能力对攻击力的增加值。

解题思路

我们可以使用动态规划算法求解该问题。首先定义一个二维数组 dp,其中 dp[i][j] 表示在前 i 种能力中,使用 j 枚金币时可以获得的最大攻击力。然后进行初始化,没有任何能力时,攻击力为0。状态转移:对于每种能力 [c, s, d],我们有两种选择:不购买该能力,状态保持不变;购买该能力,更新攻击力和攻击速度。或者我们可以使用记忆化的递归方法求解该问题。

代码实现

def solution(n, g, s, array):
    # 记忆化递归的缓存
    memo = {}
    
    def max_attack(i, g, s):
        # 如果已经计算过,直接返回结果
        if (i, g, s) in memo:
            return memo[(i, g, s)]
        
        # 如果没有更多能力可以选择
        if i == n:
            return 0
        
        # 不购买当前能力
        not_buy = max_attack(i + 1, g, s)
        
        # 购买当前能力
        c, _s, d = array[i]
        if g >= c and s + _s >= 0:  # 确保金币足够且攻击速度大于等于0
            buy = max_attack(i + 1, g - c, _s + s) + d
        else:
            buy = 0
        
        # 选择最大攻击力
        result = max(not_buy, buy)
        
        # 记录结果
        memo[(i, g, s)] = result
        return result
    
    # 从第0种能力开始递归
    return max_attack(0, g, s)

13. 线上报警问题分类

问题描述

AB 实验作为推荐策略迭代的有力工具,经常会有新的实验上线,以及一些无效策略的下线。在这些迭代过程中,难免会遇到意料之外的情况,导致部分服务崩溃而不可用。为了避免系统的全面崩溃,工程师们会添加监控报警,确保第一时间通知到值班同学进行处理。

小M同学刚刚开始负责线上报警的值班工作。很快,她就收到了第一条报警日志。在报警的时间范围内,小M同学收到了 N 名用户的反馈,每位用户编号为 1 到 N。小M同学查询线上实验后,统计了用户命中实验的列表,其中第 i 位用户命中了 kik_{i} 个实验,第 j 个实验的编号为 aija_{ij}

这些用户的反馈并不完全是由于一个问题造成的,因此小M同学需要对这些问题进行分类。根据先前的经验,小M同学会进行 Q 次询问尝试对问题进行分类,第 i 次询问会给出一个序列 bi,1,bi,2,,bi,cicib_{i,1},b_{i,2},…,b_{i,c_{i}},c_{i} 表示第 i 次查询的实验数量。当 bi,j>0b_{i,j}>0 时表示命中实验 ∣bi,jb_{i,j}∣,否则表示未命中实验 ∣bi,jb_{i,j}∣。小M同学需要得到符合这些条件的用户数。例如,序列 1, -2, 3 表示命中实验 1, 3 且未命中实验 2 的用户数量。

小M同学初来乍到,希望你能帮她解决这个问题。

解题思路

首先我们定义一个列表来存储每个查询的结果。对于每个查询,我们遍历每个用户来统计满足条件的用户数量。对于每个用户,我们设计了一个辅助函数来检查他们是否满足当前查询的条件。切片从1开始是为了跳过用户和查询列表中的第一个元素,因为第一个元素表示用户命中的实验数量或查询中的实验数量。我们只关心实际的实验编号,而不是数量。

代码实现

def solution(n, m, q, arrayN, arrayQ):
    # 初始化结果列表
    result = []
    
    # 遍历每个查询
    for query in arrayQ:
        count = 0  # 用于统计满足条件的用户数量
        
        # 遍历每个用户
        for user in arrayN:
            # 检查用户是否满足查询条件
            if is_match(user, query):
                count += 1
        
        # 将满足条件的用户数量添加到结果列表中
        result.append(count)
    
    return result

# 辅助函数:检查用户是否满足查询条件
def is_match(user, query):
    # 将用户的实验列表转换为集合
    user_set = set(user[1:])
    
    # 遍历查询中的每个条件
    for q in query[1:]:
        if q > 0:
            # 如果查询条件是正数,检查用户是否命中该实验
            if q not in user_set:
                return False
        else:
            # 如果查询条件是负数,检查用户是否未命中该实验
            if -q in user_set:
                return False
    
    # 如果所有条件都满足,返回True
    return True

14. 版本号比较

问题描述

在某个项目中,每个版本都用版本号标记,由一个或多个修订号组成,修订号之间由点号.分隔。每个修订号可能有多位数字,并且可能会包含前导零。你需要根据两个版本号 version1 和 version2,判断哪个版本更新,或者它们是否相同。

例如,2.5.33 和 0.1 都是有效的版本号。

当比较两个版本时,从左到右依次比较它们的修订号。忽略每个修订号的前导零,直接比较修订号对应的整数值。如果其中一个版本没有足够的修订号,缺失部分默认补为0

你需要根据以下规则返回比较结果:

  • 如果 version1 > version2,返回 1
  • 如果 version1 < version2,返回 -1
  • 如果两个版本相等,返回 0

解题思路

首先,我们将 version1 和 version2 按照 . 分割成修订号列表。为了方便比较,确保两个修订号列表的长度相同,较短的列表可以通过在末尾添加 0 来补齐。然后,我们从左到右逐个比较修订号,直到找到不同的修订号为止。最后根据比较结果返回 1-1 或 0

代码实现

def solution(version1, version2):
    # 将版本号按点分割成修订号列表
    v1_parts = version1.split('.')
    v2_parts = version2.split('.')
    
    # 补齐修订号列表,使其长度相同
    max_length = max(len(v1_parts), len(v2_parts))
    v1_parts += ['0'] * (max_length - len(v1_parts))
    v2_parts += ['0'] * (max_length - len(v2_parts))
    
    # 逐个比较修订号
    for i in range(max_length):
        # 将修订号转换为整数进行比较
        v1_num = int(v1_parts[i])
        v2_num = int(v2_parts[i])
        
        if v1_num > v2_num:
            return 1
        elif v1_num < v2_num:
            return -1
    
    # 如果所有修订号都相同,返回0
    return 0

15. 游戏队友搜索

问题描述

在一款多人游戏中,每局比赛需要多个玩家参与。如果发现两名玩家至少一起玩过两局比赛,则可以认为这两名玩家互为队友。现在你有一份玩家(通过玩家ID标识)和比赛局次(通过比赛ID标识)的历史记录表,目标是帮助某位指定玩家找到所有符合条件的队友。

例如,已知以下比赛历史记录:

| 玩家ID | 游戏ID

解题思路

我们可以使用字典来记录每个玩家参与的比赛。字典的键是玩家ID,值是一个集合,包含该玩家参与的所有比赛ID。首先,我们遍历输入的记录,填充字典。然后找到指定玩家参与的所有比赛ID。最后,我们遍历字典,找到与指定玩家在至少两局比赛中共同出现的其他玩家。

代码实现

def solution(id, num, array):
    # 使用字典记录每个玩家参与的比赛
    player_games = {}
    
    # 遍历输入记录,填充字典
    for player, game in array:
        if player not in player_games:
            player_games[player] = set()
        player_games[player].add(game)
    
    # 找到指定玩家参与的所有比赛ID
    target_games = player_games.get(id, set())
    
    # 初始化结果列表
    teammates = []
    
    # 遍历字典,找到与指定玩家在至少两局比赛中共同出现的其他玩家
    for player, games in player_games.items():
        if player != id:
            # 计算共同参与的比赛数量
            common_games = target_games & games
            if len(common_games) >= 2:
                teammates.append(player)
    
    # 对结果列表进行排序
    teammates.sort()
    
    return teammates

16. 字符串字符类型排序问题

问题描述

小C 需要对一个字符串进行特殊排序,这个字符串只包含三种字符类型:字母(大小写)、数字和问号。要求你按照以下规则进行排序:

  1. 问号的原始位置必须保持不变。
  2. 数字的原始位置必须保持不变,但数字要按照从大到小排序。
  3. 字母的原始位置必须保持不变,但字母要按照字典序从小到大排序。

你需要编写一个程序,帮助小C实现这个字符串的排序功能。

解题思路

首先,我们遍历输入字符串 inp,将字母和数字分别存储在不同的列表中。然后,我们对字母列表按字典序排序,对数字列表按从大到小排序。接着,我们创建两个迭代器 letter_iter 和 digit_iter,分别用于遍历排序后的字母和数字列表。最后,我们遍历原始字符串 inp,如果当前字符是字母,则从 letter_iter 中取出一个字母并添加到结果列表中;如果当前字符是数字,则从 digit_iter 中取出一个数字并添加到结果列表中;如果当前字符是问号,则直接添加到结果列表中。

代码实现

def solution(inp):
    # 分离字符类型
    letters = []
    digits = []
    
    for char in inp:
        if char.isalpha():
            letters.append(char)
        elif char.isdigit():
            digits.append(char)
    
    # 对字母和数字进行排序
    letters.sort()  # 字母按字典序从小到大排序
    digits.sort(reverse=True)  # 数字按从大到小排序
    
    # 使用迭代器
    letter_iter = iter(letters)
    digit_iter = iter(digits)
    
    # 重新组合字符串
    result = []
    
    for char in inp:
        if char.isalpha():
            result.append(next(letter_iter))
        elif char.isdigit():
            result.append(next(digit_iter))
        else:
            result.append(char)  # 问号保持不变
    
    return ''.join(result)

今天的AI刷题题目解析就到这里,小伙伴们如果有更好的解题思路,欢迎在评论区留言一起交流。