一、题目概述
小E在一个游戏中遇到了(n)个怪物,按顺序出现。每个怪物都有特定的血量和攻击力。小E的初始血量为(H),攻击力为(A)。小E需要击败这些怪物,且满足以下规则:
- 小E可以击败一个血量和攻击力都小于她当前属性的怪物。
- 第一个击败的怪物,要求其血量小于(H) 且攻击力小于(A)。
- 击败怪物后,小E会获得该怪物的属性值(即血量和攻击力加成)。
- 后续击败的怪物,要求血量和攻击力都严格大于前一个击败的怪物。
目标: 求解小E最多能击败多少个怪物。
二、解题思路
1. 问题分析
从题目给定的规则来看,小E打怪的过程类似于动态规划的一个应用。小E必须遵循一系列条件来击败怪物,且后一个怪物的血量和攻击力都必须比前一个大。因此,这个问题的核心是如何处理"击败怪物"的顺序和更新属性的问题。
问题分为两个关键步骤:
- 判断是否能击败某个怪物:只有怪物的血量和攻击力都小于小E当前的属性,才能击败。
- 更新当前的战斗状态:当击败一个怪物后,小E的血量和攻击力会提升。
2. 动态规划的设计
我们可以使用一个动态规划数组 dp 来记录击败每个怪物后的最大击败数。dp[i] 表示在击败了第i个怪物后,小E能击败的最大怪物数。
动态规划转移方程:
- 初始化:如果怪物i可以击败(即h[i] < H) 并且(a[i]<A)),则
dp[i] = 1,表示最初至少能击败怪物(i)。 - 对于每一个怪物i,遍历之前的所有怪物j,如果怪物j可以被击败并且它的血量和攻击力严格小于怪物i,则
dp[i] = max(dp[i], dp[j] + 1),即在怪物i的基础上,继承之前的最大击败数并加 1。
结束条件:
遍历所有怪物,最终的最大 dp[i] 即为小E能够击败的最多怪物数量。
3. 代码实现
function solution(n, H, A, h, a) {
let dp = new Array(n).fill(0);
let maxCount = 0; // 最大能击败的怪物数
for (let i = 0; i < n; i++) {
//如果怪物 i 的血量小于 H 且攻击力小于 A,意味着小E可以击败怪物 i
if (h[i] < H && a[i] < A) {
dp[i] = 1; // 至少击败自己这个怪物
// 检查是否可以击败前面的怪物
for (let j = 0; j < i; j++) {
// 如果怪物 j 的血量和攻击力都小于怪物 i,且小E可以击败怪物 j
if (h[j] < h[i] && a[j] < a[i]) {
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
maxCount = Math.max(maxCount, dp[i]);
}
}
return maxCount;
}
4. 代码解释
- 动态规划数组
dp:用来记录击败每个怪物后的最大击败数。 - 外层循环遍历每个怪物:判断该怪物是否能被击败,如果可以,则更新
dp[i]。 - 内层循环遍历之前的怪物:对于每个怪物i,查看之前的怪物 j,若j可以被击败,并且怪物j的血量和攻击力小于怪物i,则更新
dp[i]。 - 最终返回:遍历所有怪物,返回最大值
max_count,即最多能击败的怪物数量。
5. 时间复杂度和空间复杂度
- 外层循环:遍历所有怪物O(n)。
- 内层循环:对于每个怪物,遍历之前的怪物O(n)。
因此,总时间复杂度为O(n^2),适用于较小规模的问题。如果n很大,可能需要进一步优化。
- 总空间复杂度 = ( O(n) )(
dp数组)+ ( O(n) )(输入数组h和a)+ ( O(1) )(常数空间) - 由于输入数组h和a是题目的一部分,而
dp数组是算法实现所需的额外空间,因此总的空间复杂度为 ( O(n) ) 。
三、总结
-
动态规划思想的应用:在问题中所涉及到的状态转移和选择最优解的过程很适合使用动态规划(DP)来解决。每个怪物的击败与否都依赖于前面已经击败的怪物的选择,这正是动态规划问题的典型特征。
-
状态转移的细致性:内层循环的设计非常关键,需要确保在更新当前怪物击败数时,考虑到所有前面已经击败的怪物。同时,注意到击败怪物的顺序和属性的严格递增要求,这也是设计 DP 转移时的关键。
-
优化方向:如果n很大的情况下,可能需要考虑使用更高效的数据结构来减少内层循环的复杂度。比如,使用二分查找优化状态转移的过程,或者采用贪心策略减少不必要的遍历。
这道题通过合理地利用动态规划,设计了合适的状态转移方程,有效地解决了怪物击败顺序和属性递增的问题。通过这个题目,能让我们更加深入地理解动态规划的应用场景,并对如何处理复杂约束条件有了更清晰的认识。在面对类似问题时,应该首先明确状态定义和转移关系,逐步推导出最终的解决方案。