1. 题目背景与核心需求
这道来自拼多多2026年春招的编程题,考察的是在实际业务场景下的算法应用能力。题目描述的是服务器扩容场景:假设有若干台服务器,每台服务器有固定的存储容量,现在需要进行扩容操作,每次扩容可以选择任意一台服务器,将其容量翻倍。要求在限定次数的扩容操作内,使得所有服务器的总容量最大化。
这类问题在实际工程中非常常见,比如:
- 云计算资源分配优化
- CDN节点带宽扩容
- 分布式存储容量规划
2. 解题思路分析
2.1 问题转化与贪心策略
这个问题可以转化为典型的贪心算法问题。要使总容量最大化,每次扩容都应该选择当前能够带来最大增益的服务器进行操作。具体来说:
- 每次选择当前容量最大的服务器进行扩容
- 因为容量翻倍的增益与当前容量成正比(Δ=2x-x=x)
- 使用最大堆(优先队列)可以高效实现这个策略
2.2 数学证明
假设有两台服务器容量分别为a和b(a>b),扩容次数剩余k次:
- 方案1:全部k次给a扩容 → 总容量 = a×2^k + b
- 方案2:分次扩容 → 总容量 = a×2^m + b×2^(k-m)
通过数学归纳法可以证明,当a>b时,方案1总是优于方案2。这验证了贪心策略的正确性。
3. 代码实现与优化
3.1 Java实现
import java.util.PriorityQueue; import java.util.Collections; public class Solution { public int maxTotalCapacity(int[] servers, int k) { PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Collections.reverseOrder()); for (int capacity : servers) { maxHeap.offer(capacity); } while (k-- > 0) { int max = maxHeap.poll(); maxHeap.offer(max * 2); } int total = 0; while (!maxHeap.isEmpty()) { total += maxHeap.poll(); } return total; } }时间复杂度分析:
- 建堆:O(n)
- 每次操作:O(log n)
- 总复杂度:O(n + k log n)
3.2 C++实现
#include <queue> #include <vector> using namespace std; class Solution { public: int maxTotalCapacity(vector<int>& servers, int k) { priority_queue<int> max_heap; for (int cap : servers) { max_heap.push(cap); } while (k--) { int max_cap = max_heap.top(); max_heap.pop(); max_heap.push(max_cap * 2); } int total = 0; while (!max_heap.empty()) { total += max_heap.top(); max_heap.pop(); } return total; } };3.3 Python实现
import heapq def maxTotalCapacity(servers, k): max_heap = [] for cap in servers: heapq.heappush(max_heap, -cap) # 模拟最大堆 for _ in range(k): max_cap = -heapq.heappop(max_heap) heapq.heappush(max_heap, -(max_cap * 2)) return sum(-x for x in max_heap)4. 边界条件与异常处理
在实际编码中需要考虑以下特殊情况:
- 空服务器列表:直接返回0
- k=0:直接返回当前总容量
- 大数处理:当容量可能超过int范围时,需要使用long类型
- 性能优化:当k远大于n时,可以提前终止(当堆顶为0时)
改进后的Java代码:
public long maxTotalCapacity(int[] servers, int k) { if (servers == null || servers.length == 0) return 0L; PriorityQueue<Long> maxHeap = new PriorityQueue<>(Collections.reverseOrder()); for (int cap : servers) { maxHeap.offer((long)cap); } while (k-- > 0 && maxHeap.peek() > 0) { long max = maxHeap.poll(); maxHeap.offer(max * 2); } long total = 0L; while (!maxHeap.isEmpty()) { total += maxHeap.poll(); } return total; }5. 复杂度优化思考
对于极端情况(如k=1e9),上述解法可能不够高效。可以考虑以下优化:
- 数学推导法:找出最终各服务器被扩容的次数比例
- 快速幂计算:当k足够大时,可以推导出最终容量为 max2^m + others2^n 的形式
- 分组处理:将相同容量的服务器分组处理
优化后的Python实现:
from collections import defaultdict import math def maxTotalCapacity(servers, k): if not servers: return 0 freq = defaultdict(int) for cap in servers: freq[cap] += 1 unique_caps = sorted(freq.keys(), reverse=True) while k > 0 and len(unique_caps) > 1: max_cap = unique_caps[0] next_cap = unique_caps[1] # 计算需要多少次扩容可以使max_cap*2^m <= next_cap*2^n delta = max_cap - next_cap m = math.floor(math.log2(delta / max_cap + 1)) if delta > 0 else 1 m = max(1, min(m, k)) freq[max_cap * (2 ** m)] += freq[max_cap] del freq[max_cap] unique_caps = sorted(freq.keys(), reverse=True) k -= m # 处理剩余的k次 if k > 0: max_cap = unique_caps[0] freq[max_cap * (2 ** k)] += freq[max_cap] del freq[max_cap] return sum(cap * count for cap, count in freq.items())6. 测试用例设计
全面的测试应该包括:
常规测试
- 输入:[1,2,3], k=1 → 输出:1 + 2 + 6 = 9
- 输入:[4,2,8], k=3 → 输出:32 + 2 + 8 = 42
边界测试
- 输入:[], k=100 → 输出:0
- 输入:[0,0,0], k=5 → 输出:0
- 输入:[1], k=1e9 → 输出:2^1e9 (注意大数处理)
性能测试
- 输入:1e5个随机数(1-1e6), k=1e5 → 测试运行时间
7. 实际工程应用扩展
在实际系统设计中,这个问题可以有多种变体:
- 带成本的扩容:不同服务器扩容成本不同
- 多维度约束:同时考虑CPU、内存、存储的扩容
- 动态规划变体:考虑扩容操作的时序性
例如带成本的扩容问题:
def maxTotalCapacityWithCost(servers, costs, budget): # servers[i]是容量,costs[i]是单次扩容成本 heap = [] for cap, cost in zip(servers, costs): heapq.heappush(heap, (-cap, cost)) total = 0 while budget > 0 and heap: neg_cap, cost = heapq.heappop(heap) cap = -neg_cap if cost > budget: total += cap continue times = budget // cost new_cap = cap * (2 ** times) budget -= times * cost heapq.heappush(heap, (-new_cap, cost)) total += sum(-cap for cap, _ in heap) return total8. 面试考察要点
这道题在面试中主要考察:
- 问题分析能力:能否将实际问题抽象为算法问题
- 数据结构选择:优先队列的使用场景
- 边界条件处理:异常输入的考虑
- 复杂度分析:时间和空间复杂度的计算
- 编码实现:代码的整洁度和正确性
在面试中,建议按照以下步骤解答:
- 先明确问题要求和输入输出
- 提出暴力解法并分析不足
- 逐步优化思路,讨论贪心策略的正确性
- 选择合适的数据结构实现
- 编写代码并测试边界条件
- 讨论可能的优化和扩展
9. 常见错误与调试技巧
新手在解决这个问题时容易犯的错误:
- 错误的数据结构选择:使用数组每次排序(O(n log n) per operation)
- 整数溢出:没有使用long类型处理大数
- 边界条件遗漏:空输入或k=0的情况
- 过早优化:一开始就考虑复杂情况而忽略基础解法
调试技巧:
- 打印堆状态:每次操作后输出堆内容
- 小规模测试:先用简单例子验证
- 性能分析:对于大数据量测试运行时间
10. 算法扩展学习
类似的问题可以延伸学习:
- 霍夫曼编码:类似的贪心策略
- 任务调度:优先处理收益高的任务
- 投资组合优化:有限资源下的最优分配
- 背包问题变体:带约束的资源分配
推荐练习题:
- LeetCode 1648: Sell Diminishing-Valued Colored Balls
- LeetCode 1383: Maximum Performance of a Team
- LeetCode 857: Minimum Cost to Hire K Workers