2026-01-14:排序排列。用go语言,有两个等长的整数数组 value 和 limit,表示 n 个元素的得分和操作限制。初始时所有元素都处于未激活状态,你可以按任意顺序逐个激活它们,但激活第 i 个元素时,当前处于“活跃”状态的元素数量必须严格小于 limit[i]。
每次激活第 i 个元素时,就把 value[i] 加入累计得分(即统计所有曾被激活过的元素的 value 之和)。
此外,每次激活操作结束后,若此刻活跃元素的数量为 x,那么所有满足 limit[j] <= x 的元素 j 都会被立即并永久设为非活跃(包括那些刚才可能处于活跃状态的元素),并且这些被置为非活跃的元素以后不能再次激活。请计算通过选择最优的激活顺序能够得到的最大累计得分。
1 <= n == value.length == limit.length <= 100000。
1 <= value[i] <= 100000。
1 <= limit[i] <= n。
输入: value = [3,5,8], limit = [2,1,3]。
输出: 16。
解释:
一个最优的激活顺序是:
| 步骤 | 激活的 i | value[i] | 激活 i 前的活跃数 | 激活 i 后的活跃数 | 变为非活跃的 j | 非活跃元素 | 总和 |
|---|---|---|---|---|---|---|---|
| 1 | 1 | 5 | 0 | 1 | j = 1(因为 limit[1] = 1) | [1] | 5 |
| 2 | 0 | 3 | 0 | 1 | — | [1] | 8 |
| 3 | 2 | 8 | 1 | 2 | j = 0(因为 limit[0] = 2) | [0, 1] | 16 |
因此,可能的最大总和是 16。
题目来自力扣3645。
算法步骤说明
该算法的核心思路是按限制条件(limit)分组处理,并在每组内部优先选择价值(value)高的元素。具体步骤如下:
-
分组 首先,算法根据每个元素的
limit值进行分组。limit值相同的元素会被分到同一组。例如,在示例中,limit数组为[2, 1, 3],那么limit为 1、2、3 的元素会分别被归入不同的组。groups[lim]这个切片就存储了所有limit值为lim的元素的value。 -
组内排序与选择 接着,算法遍历每一个分组。对于某个分组,其键为
lim(即限制条件),值为该组内所有元素的value列表a。- 关键判断:如果该组的限制条件
lim小于组内元素的数量len(a),这意味着我们不能激活组内的所有元素,否则在激活最后一个元素时,活跃元素数量将等于lim,导致该组(甚至其他组)的元素被全部置为非活跃。因此,我们必须做出选择。 - 贪心选择:为了最大化总得分,最优策略是只保留该组中价值最大的
lim个元素。这是典型的贪心算法思想,即在局部做出最优决策(选择价值最高的),从而期望获得全局最优解。实现上,先将组内元素按价值从小到大排序,然后只取排序后末尾的lim个元素。
- 关键判断:如果该组的限制条件
-
累计得分 在处理完每个组(即完成组内筛选后),算法会将该组最终保留的所有元素的价值累加到总得分
ans中。因为每个元素只能被激活一次,且被激活后其价值就被永久计入总分,所以这里直接求和即可。
时间复杂度与空间复杂度分析
-
总的时间复杂度:
- 分组:遍历
limit数组并将value放入对应的组中,时间复杂度为 O(n)。 - 组内处理:这是主要开销所在。最坏情况下,所有元素都具有相同的
limit值,那么只需要对一个包含 n 个元素的组进行排序。使用 Go 标准库的快速排序平均时间复杂度为 O(n log n)。其他组的处理时间可以忽略。因此,总的时间复杂度为 O(n log n)。
- 分组:遍历
-
总的额外空间复杂度:
- 存储分组:需要额外的空间来存储分组
groups,其大小取决于limit的取值范围。由于limit[i] <= n,所以groups切片的最大长度为 n+1。最坏情况下,每个元素都属于不同的组,因此总共需要存储 n 个元素的引用,空间复杂度为 O(n)。
- 存储分组:需要额外的空间来存储分组
综上所述,该算法通过巧妙的分组和贪心选择策略,高效地解决了在特定激活规则下最大化总得分的问题。其总的时间复杂度为 O(n log n),总的额外空间复杂度为 O(n)。
Go完整代码如下:
package main
import (
"fmt"
"slices"
)
func maxTotal(value, limit []int) (ans int64) {
groups := make([][]int, len(value)+1)
for i, lim := range limit {
groups[lim] = append(groups[lim], value[i])
}
for lim, a := range groups {
if lim < len(a) {
// 只取最大的 lim 个数
slices.Sort(a)
a = a[len(a)-lim:]
}
for _, x := range a {
ans += int64(x)
}
}
return
}
func main() {
value := []int{3, 5, 8}
limit := []int{2, 1, 3}
result := maxTotal(value, limit)
fmt.Println(result)
}
Python完整代码如下:
# -*-coding:utf-8-*-
from typing import List
def max_total(value: List[int], limit: List[int]) -> int:
ans = 0
n = len(value)
groups = [[] for _ in range(n + 1)]
# 按照 limit 分组
for i in range(n):
groups[limit[i]].append(value[i])
# 处理每个分组
for lim, a in enumerate(groups):
if not a:
continue
if lim < len(a):
# 只取最大的 lim 个数
a.sort()
a = a[-lim:]
ans += sum(a)
return ans
def main():
value = [3, 5, 8]
limit = [2, 1, 3]
result = max_total(value, limit)
print(result)
if __name__ == "__main__":
main()
C++完整代码如下:
#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
using namespace std;
long long maxTotal(vector<int>& value, vector<int>& limit) {
int n = value.size();
vector<vector<int>> groups(n + 1);
for (int i = 0; i < n; i++) {
groups[limit[i]].push_back(value[i]);
}
long long ans = 0;
for (int lim = 0; lim < groups.size(); lim++) {
auto& a = groups[lim];
if (a.empty()) continue;
if (lim < a.size()) {
sort(a.begin(), a.end(), greater<int>()); // 降序排序
ans += accumulate(a.begin(), a.begin() + lim, 0LL);
} else {
ans += accumulate(a.begin(), a.end(), 0LL);
}
}
return ans;
}
int main() {
vector<int> value = {3, 5, 8};
vector<int> limit = {2, 1, 3};
long long result = maxTotal(value, limit);
cout << result << endl;
return 0;
}