问题理解
本题的核心在于模拟小E在游戏中击败怪物的过程,并计算她最多能击败多少怪物。每个怪物都有特定的血量和攻击力,小E在击败怪物后会获得该怪物的属性值。为了保持战斗节奏,要求击败的怪物序列中,后一个怪物的血量和攻击力都必须严格大于前一个怪物。
数据结构选择
-
动态规划(DP) :
- 使用动态规划来解决最长递增子序列(LIS)问题。
dp[i]表示考虑前i个怪物,且需要击败第i个怪物,此时的最大击败数量。
-
数组:
- 使用两个数组
h和a分别存储怪物的血量和攻击力。 - 使用一个数组
dp来存储每个怪物对应的击败数量。
- 使用两个数组
算法步骤
-
初始化:
- 初始化一个
dp数组,长度为n,初始值为0。 - 初始化一个变量
ans用于记录最大击败数量,初始值为0。
- 初始化一个
-
遍历怪物:
- 对于每个怪物
i,检查其血量h[i]和攻击力a[i]是否小于小E的当前属性H和A。 - 如果小E无法击败当前怪物(即
h[i] >= H或a[i] >= A),则跳过该怪物。
- 对于每个怪物
-
动态规划更新:
- 如果小E可以击败当前怪物,则将
dp[i]初始化为1。 - 对于每个之前的怪物
j(j < i),如果h[i] > h[j]且a[i] > a[j],则更新dp[i]为dp[j] + 1和当前dp[i]的最大值。
- 如果小E可以击败当前怪物,则将
-
更新最大值:
- 每次更新
dp[i]后,更新ans为dp[i]和当前ans的最大值。
- 每次更新
-
返回结果:
- 最终返回
ans,即小E最多能击败的怪物数量。
- 最终返回
复杂度分析
- 时间复杂度:
O(n^2),其中n是怪物的数量。因为需要两层循环来更新dp数组。 - 空间复杂度:
O(n),用于存储dp数组。
优化思路
-
二分查找优化:
- 可以使用二分查找来优化最长递增子序列的查找过程,将时间复杂度降低到
O(n log n)。 - 维护一个数组
tails,tails[i]表示长度为i+1的递增子序列的最后一个元素的最小值。
- 可以使用二分查找来优化最长递增子序列的查找过程,将时间复杂度降低到
-
状态压缩:
- 如果怪物数量非常大,可以考虑使用状态压缩来减少空间复杂度。
public class Main {
public static int solution(int n, int H, int A, int[] h, int[] a) {
// write code here
// 动态规划,最长递增子序列
// dp[i] 表示考虑前 i 个怪物,且需要击败第 i 个怪物,此时的最大击败数量
int ans = 0;
int[] dp = new int[n];
for (int i = 0; i < n; i++) {
// 打不过当前怪物,选择跳过
if (h[i] >= H || a[i] >= A) {
continue;
}
dp[i] = 1;
for (int j = 0; j < i; j++) {
if (h[i] > h[j] && a[i] > a[j]) {
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);
}
}
总结
本题通过动态规划的思想,结合最长递增子序列的特性,有效地解决了小E在游戏中击败怪物的问题。通过合理的初始化和状态转移,可以在 O(n^2) 的时间复杂度内得到结果。如果需要进一步优化,可以考虑使用二分查找来降低时间复杂度。整体思路清晰,逻辑严谨,适合用于理解和练习动态规划的基本思想和应用。