跳到主要内容

小美的数组删除

美团 2024-08-10 后端笔试编程题(编程题第 2 题)

题目描述​

小美有一个长度为 n 的数组 a,她可以对数组进行如下操作:

  • 删除第一个元素 a[1],同时数组长度减一,花费为 x;
  • 删除整个数组,花费为 k * MEX(a),其中 MEX(a) 表示 a 中未出现过的 最小非负整数(例如 [0,1,2,4] 的 MEX 为 3)。

小美想知道将数组全部清空的最小代价。

输入格式​

第一行一个整数 T,代表数据组数。 每组数据:

  • 第一行三个正整数 n、k、x(1 ≤ n ≤ 2×10⁵,1 ≤ k, x ≤ 10⁹);
  • 第二行 n 个整数 a[i](0 ≤ a[i] ≤ n)。

保证所有数据组的 n 之和不超过 2×10⁵。

输出格式​

对每组数据,在一行输出一个整数,表示清空数组的最小花费。

示例​

示例 1​

输入:

1
6 3 3
4 5 2 3 1 0

输出:

15

说明: 不删元素直接整段删除,MEX{4,5,2,3,1,0} = 6,花费 18; 先删掉第一个元素 4,再整段删除,MEX{5,2,3,1,0} = 4,花费 3 + 12 = 15,最省。


解题思路​

第一步:状态定义​

操作只会发生在「数组的前缀被删掉之后」—— 剩下的永远是原数组的一个后缀。 设 f[i] 表示删光后缀 a[i..n-1](下标 0 起)的最小花费。

第二步:转移​

面对后缀 a[i..n-1],只有两种「下一步」:

f[i] = min( x + f[i+1],                    # 先单独删掉 a[i]
k * MEX(a[i..n-1]) ) # 直接整段删除
  • 单独删 a[i] 后,问题变成更短的后缀,所以是 x + f[i+1];
  • 或者直接一步删掉整段,代价是当前后缀的 MEX 乘以 k。

边界 f[n] = 0(空数组不用花钱)。答案就是 f[0]。

第三步:怎么快速求每个后缀的 MEX​

如果每个后缀都重新算 MEX,总复杂度 O(n²),会超时。

关键观察:i 从 n-1 递减到 0 时,后缀是不断变大的 (每次只多一个元素 a[i])。因此 MEX 只会单调不减 —— 用一个指针 mex 边走边扩即可:

  • 加入 a[i] 后,如果它正好等于当前 mex,就让 mex 一直自增, 直到遇到一个「没在后缀里出现过」的值。

每个值最多让 mex 前进一步,所以整个循环是 O(n) 的。


完整代码实现​

算法可视化 · 逐帧回放这道题的代码70 帧 · 录制输入
import sys


def solve() -> None:
"""后缀 DP + 单调递增的 MEX 指针"""
data = sys.stdin.read().split()
if not data:
return
idx = 0
t = int(data[idx]); idx += 1
out = []
for _ in range(t):
n = int(data[idx]); k = int(data[idx + 1]); x = int(data[idx + 2]); idx += 3
a = [int(v) for v in data[idx:idx + n]]; idx += n

present = [False] * (n + 2)
mex = 0
f_next = 0 # f[n]
for i in range(n - 1, -1, -1):
v = a[i]
if v < len(present):
present[v] = True
while mex < len(present) and present[mex]:
mex += 1
f_cur = min(x + f_next, k * mex)
f_next = f_cur
out.append(str(f_next))
sys.stdout.write("\n".join(out) + "\n")


if __name__ == "__main__":
solve()

示例推演​

a = [4,5,2,3,1,0],k = 3,x = 3,从后往前算:

i后缀MEX单独删 (x+f[i+1])整段删 (k·MEX)f[i]
6空———0
5[0]13 + 0 = 333
4[1,0]23 + 3 = 666
3[3,1,0]23 + 6 = 966
2[2,3,1,0]43 + 6 = 9129
1[5,2,3,1,0]43 + 9 = 121212
0[4,5,2,3,1,0]63 + 12 = 151815

答案 f[0] = 15,与样例一致。


复杂度分析​

解法时间复杂度空间复杂度说明
每个后缀重算 MEXO(n²)O(n)超时
后缀 DP + 单调 MEXO(n)O(n)推荐

易错点总结​

1. 每一轮都重新求 MEX​

这是最容易踩的性能坑。后缀是逐步变大的,MEX 只会增大, 用一个指针维护即可,不要每次重算。

2. 把 MEX 当成「最大值 + 1」​

MEX 是「未出现的最小非负整数」,与最大值无关。 例如 [0,1,2,4] 的 MEX 是 3,而不是 5。

3. 只考虑「整段删除」或「只删单个」​

两种操作要组合比较最小值;只选一种会漏掉更优解。

4. 多组数据的清理​

present 数组和 mex 每组都要重新初始化,不要跨组复用。


扩展思考​

  • 若「删除整个数组」的代价改成与元素个数有关(如 k * 长度), 转移会相应变化。
  • 「动态维护 MEX」是常见技巧:只要是单向只会变大的场景, 都可以用单调指针把均摊复杂度降到 O(1)。

相关题目​

自测​

加载评论中...