AI刷题6 小E的怪物挑战|豆包MarsCode AI刷题

88 阅读5分钟

小E的怪物挑战:动态规划解法解析

在许多角色扮演游戏(RPG)中,玩家通常会遇到一个个不断增强的敌人,需要通过合理的策略击败它们才能获得奖励和提升。在这类问题中,玩家的能力和敌人的属性是非常重要的决定因素。在这篇文章中,我们将解析一个典型的动态规划问题——小E的怪物挑战

问题描述

小E在一场游戏中遇到了 n个按顺序出现的怪物。每个怪物都有其特定的血量 h_i 和攻击力 a_i。小E的初始血量为 H,攻击力为 A。游戏规则如下:

  1. 小E可以击败一个血量和攻击力都小于她当前属性的怪物。
  2. 对于第一个击败的怪物,需要满足其血量小于 H 且攻击力小于 A。
  3. 击败怪物后,小E会获得该怪物的属性值(血量和攻击力)。
  4. 为了保持战斗节奏,要求击败的怪物序列中,后一个怪物的血量和攻击力都必须严格大于前一个怪物。

目标:小E希望知道,她最多能击败多少个怪物。

解题思路

这个问题本质上是一个最长递增子序列(LIS)的问题,但是它有两个维度:血量和攻击力。我们需要考虑如何选择怪物,使得选择的怪物在血量和攻击力上都满足递增条件。

  1. 状态定义:

    • 使用动态规划(DP)来解决这个问题。我们定义一个数组 dp[i],其中 dp[i] 表示以第 i 个怪物为结尾,且满足条件的最长击败怪物序列的长度。
  2. 状态转移:

    • 初始时,小E的血量和攻击力分别为 H 和 A,因此,小E只能击败那些血量小于 H 且攻击力小于 A的怪物。
    • 对于每一个怪物 i,我们从前到后遍历其之前的怪物 j,检查是否可以将怪物 i 加入到以怪物 j 为结尾的序列中。如果 h[j] < h[i] 且 a[j] < a[i],那么 dp[i] 可以通过更新来扩展这个序列,即 dp[i] = max(dp[i], dp[j] + 1)
  3. 最终结果:

    • 最终的答案是 dp 数组中的最大值,表示可以击败的最大怪物数量。

关键点分析

  1. 状态的有效性:

    • 对于每个怪物,我们需要确保其血量和攻击力小于小E的当前属性,且所有选择的怪物序列中的怪物属性必须递增。
  2. 递增子序列:

    • 因为每次选择怪物时,我们必须确保其血量和攻击力都严格大于前一个怪物的属性,因此这使得问题转化为一个二维递增子序列的问题。
  3. 复杂度分析:

    • 时间复杂度是 O(n^2),因为我们对于每个怪物都需要检查之前的所有怪物,进行状态转移。

代码实现

以下是解决该问题的代码实现:

public class Main {
    public static int solution(int n, int H, int A, int[] h, int[] a) {
        int ans = 0;
        int[] dp = new int[n];  // 动态规划数组
        
        // 遍历每个怪物
        for (int i = 0; i < n; i++) {
            // 如果该怪物的血量或攻击力超过了小E的能力,跳过该怪物
            if (h[i] >= H || a[i] >= A) {
                continue;
            }
            
            dp[i] = 1;  // 初始状态下,单独的怪物自己构成一个子序列
            // 查看当前怪物是否可以接在之前的怪物后面
            for (int j = 0; j < i; j++) {
                if (h[j] < h[i] && a[j] < a[i]) {
                    dp[i] = Math.max(dp[i], dp[j] + 1);  // 更新最大子序列长度
                }
            }
            ans = Math.max(ans, dp[i]);  // 更新全局最大子序列长度
        }
        
        return ans;  // 返回结果
    }

    public static void main(String[] args) {
        // 测试样例
        System.out.println(solution(3, 4, 5, new int[]{1, 2, 3}, new int[]{3, 2, 1}) == 1);
        System.out.println(solution(5, 10, 10, new int[]{6, 9, 12, 4, 7}, new int[]{8, 9, 10, 2, 5}) == 2);
        System.out.println(solution(4, 20, 25, new int[]{10, 15, 18, 22}, new int[]{12, 18, 20, 26}) == 3);
    }
}

代码详解

  1. 初始化动态规划数组:

    • 我们首先初始化一个大小为 n 的数组 dp,其中 dp[i] 表示以第 i 个怪物为末尾的最长递增子序列的长度。初始时每个怪物自己就是一个子序列,因此 dp[i] = 1
  2. 遍历怪物:

    • 对于每个怪物 i,首先检查它是否能被小E击败。若 h[i] < H 且 a[i] < A,则才有资格加入。
  3. 状态转移:

    • 对于每个怪物 i,我们遍历之前的所有怪物 j,检查 h[j] < h[i] 且 a[j] < a[i] 是否成立,如果成立则表示怪物 i 可以接在怪物 j 后面,更新 dp[i]
  4. 更新最大值:

    • 在每次更新 dp[i] 后,我们通过 ans = Math.max(ans, dp[i]) 来更新当前最大子序列长度。
  5. 返回结果:

    • 最后返回 ans,即最大击败怪物的数量。

复杂度分析

  • 时间复杂度: O(n^2),外层循环遍历所有怪物,内层循环遍历每个怪物之前的所有怪物,因此总的时间复杂度为 O(n^2)。
  • 空间复杂度: O(n),我们只用了一个大小为 n 的 dp 数组来存储状态,因此空间复杂度为 O(n)。

总结与优化

虽然当前的解法已经可以正确地解决问题,但它的时间复杂度较高,O(n^2)。对于较大的输入,可能会导致程序运行较慢。为了优化性能,可以考虑使用二分查找树状数组等技术来进一步提升效率。不过,对于一般规模的输入,这种动态规划的解法已经足够有效。

通过本问题,我们不仅学习了如何解决二维递增子序列问题,也深入理解了如何使用动态规划来优化子问题的求解。