1. 题目背景与核心需求解析

这道来自华为2025秋招的编程题模拟了一个简化版塔防游戏场景。题目要求考生设计算法计算在特定条件下防御塔能够消灭的最大敌人数。作为典型的动态规划与贪心算法结合题型,它考察了以下几个核心能力:

  • 对游戏规则和边界条件的准确理解
  • 将实际问题抽象为数学模型的能力
  • 时空复杂度优化的实现技巧
  • 多语言编码的熟练度(Java/C++/Python)

1.1 游戏规则拆解

题目设定包含三个关键参数:

  1. 防御塔攻击范围(radius)
  2. 敌人移动速度(speed)
  3. 敌人出现波次(waves)

每个波次包含若干沿固定路径移动的敌人,防御塔每次攻击可以消灭范围内所有敌人。我们需要计算在最优攻击时机选择下,塔能消灭的最大敌人数。

关键细节:敌人移动是连续的而非离散的,这意味着攻击时机可以是任意时间点,这显著增加了问题复杂度。

1.2 数学模型抽象

将问题转化为数学模型时需要明确:

  • 敌人位置是时间的函数 position = speed × time
  • 攻击有效条件为 |position| ≤ radius
  • 每个波次的敌人有固定生命周期(进入和离开攻击范围的时间窗口)

这本质上是一个区间调度问题(Interval Scheduling),需要找出不重叠区间的最优组合。

2. 算法设计与复杂度分析

2.1 贪心算法解决方案

最直观的解法是贪心算法:

  1. 计算每个波次敌人的时间窗口 [entry_time, exit_time]
  2. 按照exit_time升序排序
  3. 遍历选择不冲突的区间
def max_enemies(radius, speed, waves):
    intervals = []
    for count in waves:
        entry = radius / speed
        exit = 3 * radius / speed  # 假设路径长度为3倍半径
        intervals.append((entry, exit, count))
    
    intervals.sort(key=lambda x: x[1])
    
    last_attack = -float('inf')
    total = 0
    for entry, exit, count in intervals:
        if entry >= last_attack:
            total += count
            last_attack = exit
    return total

时间复杂度:O(nlogn) (排序主导) 空间复杂度:O(n)

2.2 动态规划优化

当波次间存在复杂依赖关系时,可以采用DP:

public int maxEnemies(int radius, int speed, int[] waves) {
    // 预处理所有区间
    List<Interval> intervals = new ArrayList<>();
    for (int i = 0; i < waves.length; i++) {
        double entry = (double)radius / speed;
        double exit = 3 * entry;
        intervals.add(new Interval(entry, exit, waves[i], i));
    }
    
    // 按结束时间排序
    Collections.sort(intervals, (a,b) -> Double.compare(a.end, b.end));
    
    // DP数组:dp[i]表示前i个区间的最大值
    int[] dp = new int[waves.length + 1];
    for (int i = 1; i <= intervals.size(); i++) {
        Interval current = intervals.get(i-1);
        int prev = findLastNonOverlapping(intervals, i-1);
        dp[i] = Math.max(dp[i-1], dp[prev+1] + current.count);
    }
    return dp[waves.length];
}

该方案时间复杂度O(n^2),适合波次间有重叠的特殊场景。

3. 多语言实现对比

3.1 Java实现要点

// 处理浮点数精度问题
double entry = Math.round((radius / (double)speed) * 10000) / 10000.0;

// 使用TreeMap优化查找
TreeMap<Double, Integer> timeline = new TreeMap<>();
for (Interval interval : intervals) {
    Double floorKey = timeline.floorKey(interval.start);
    int prevCount = (floorKey != null) ? timeline.get(floorKey) : 0;
    if (prevCount + interval.count > timeline.getOrDefault(interval.end, 0)) {
        timeline.put(interval.end, prevCount + interval.count);
    }
}

3.2 C++注意事项

// 避免浮点误差累积
constexpr double EPS = 1e-9;
bool almostEqual(double a, double b) {
    return fabs(a - b) < EPS;
}

// 使用lower_bound进行高效搜索
auto it = upper_bound(events.begin(), events.end(), current.start, 
    [](double val, const Event& e) { return val < e.end + EPS; });

3.3 Python优化技巧

# 使用bisect模块加速查找
import bisect

end_times = [i[1] for i in intervals]
idx = bisect.bisect_right(end_times, current.start) - 1
prev_count = dp[idx] if idx >= 0 else 0

4. 边界条件与测试用例

4.1 必须考虑的异常场景

  1. 速度为0时的除零保护
  2. 波次为空数组的情况
  3. 超大radius导致的数值溢出
  4. 多个波次完全重叠时的计数

4.2 典型测试案例

Test Cases:
1. 常规案例
   Input: radius=5, speed=2, waves=[3,1,4]
   Output: 8 (选择第1和第3波)

2. 全重叠案例
   Input: radius=3, speed=1, waves=[2,2,2]
   Output: 2 (只能选一个波次)

3. 边缘时机
   Input: radius=10, speed=5, waves=[1,1,1]
   Output: 3 (完美衔接)

5. 面试考察要点解析

这道题在华为面试中价值200分,主要评估:

  1. 问题分析能力

    • 能否识别出这是区间调度问题的变种
    • 对连续时间模型的处理方法
  2. 算法优化意识

    • 从O(n^2)到O(nlogn)的优化思路
    • 浮点数精度的特殊处理
  3. 工程实现细节

    • 多语言特性的合理运用
    • 边界条件的完备性检查
  4. 沟通表达能力

    • 变量命名的清晰度
    • 代码注释的完整性

实际面试中,面试官可能会追问:如果敌人移动速度各不相同该如何处理?这时问题会升级为加权区间调度,需要结合优先队列进行优化。

6. 性能优化进阶方案

对于极端大规模数据(如1e5波次),可以考虑:

  1. 离散化处理
vector<double> time_points;
for (auto& interval : intervals) {
    time_points.push_back(interval.start);
    time_points.push_back(interval.end);
}
sort(time_points.begin(), time_points.end());
time_points.erase(unique(time_points.begin(), time_points.end()), time_points.end());
  1. 线段树优化
class SegmentTree {
    // 实现区间查询和单点更新
    public int queryMax(int l, int r) { ... }
    public void update(int pos, int val) { ... }
}

// 查询区间最大值并更新
int prev_max = segTree.queryMax(0, discretized_index);
segTree.update(current_end_index, prev_max + current_count);
  1. 并行预处理
from concurrent.futures import ThreadPoolExecutor

def process_wave(wave):
    entry = radius / speed
    exit = 3 * entry
    return (entry, exit, wave)

with ThreadPoolExecutor() as executor:
    intervals = list(executor.map(process_wave, waves))

7. 不同岗位的解题侧重

根据华为不同岗位要求,解答时可突出:

  1. 通用软件开发

    • 强调代码可读性和健壮性
    • 完善的异常处理机制
  2. 嵌入式软件

    • 关注内存使用效率
    • 避免动态内存分配
  3. 测试开发

    • 设计全面的测试用例
    • 边界值分析和等价类划分
  4. 数据科学

    • 算法的时间复杂度证明
    • 大数据量下的扩展方案
  5. 算法工程

    • 多种解法的对比分析
    • 近似算法的设计思路

8. 常见错误与调试技巧

8.1 典型错误模式

  1. 浮点数比较使用 == 而非误差范围
  2. 未考虑波次count为0的特殊情况
  3. 排序时仅按开始时间而非结束时间
  4. 数组越界(特别是C++实现)

8.2 调试建议

  1. 可视化测试案例:
import matplotlib.pyplot as plt

for i, (s,e,c) in enumerate(intervals):
    plt.plot([s,e], [i,i], label=f'Wave {c}')
plt.axvline(x=attack_time, color='r')
plt.show()
  1. 使用防御性编程:
assert speed > 0 : "Speed must be positive";
Objects.requireNonNull(waves, "Waves array cannot be null");
  1. 日志调试法:
#define DEBUG_LOG(fmt, ...) \
    fprintf(stderr, "[DEBUG] %s:%d: " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__)

DEBUG_LOG("Processing wave %d: entry=%.2f exit=%.2f", i, entry, exit);

9. 学习路径建议

针对想系统准备此类题目的同学,推荐:

  1. 基础夯实

    • 《算法导论》贪心算法章节
    • LeetCode经典区间问题:
        1. Non-overlapping Intervals
        1. Minimum Number of Arrows to Burst Balloons
  2. 专项突破

    • 浮点数精度处理专题
    • 调度问题变种总结
    • 多语言实现对比
  3. 实战演练

    • 华为OJ历史题库
    • Codeforces贪心算法标签
    • AtCoder DP专项比赛
  4. 延伸阅读

    • 游戏AI中的路径规划
    • 实时策略游戏的算法优化
    • 离散事件仿真技术

10. 工程实践中的变种

实际游戏开发中会遇到更复杂场景:

  1. 移动速度变化
# 变速敌人处理
def get_position(time):
    if time < 0.5: return speed1 * time
    else: return speed1*0.5 + speed2*(time-0.5)
  1. 多防御塔协同
class Tower {
    double x, y, radius;
    List<Enemy> trackEnemies(List<Enemy> waves) {
        return waves.stream()
                   .filter(e -> distance(e) <= radius)
                   .collect(Collectors.toList());
    }
}
  1. 敌人分批释放
// 时间轴事件处理
struct GameEvent {
    double trigger_time;
    function<void()> action;
};

priority_queue<GameEvent, vector<GameEvent>, greater<GameEvent>> event_queue;

这类问题在工业界的实际应用远比笔试题目复杂,但核心算法思想是相通的。掌握基础解法后,再逐步扩展到更真实的业务场景,是成长为优秀工程师的必经之路。

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐