这道题目来自华为OD(Online Judge)机考双机位C卷的结对编程环节,考察的是对数组操作和组合数学的理解能力。题目要求我们计算在特定条件下可以组成的三元组数量,这在算法面试中属于中等难度的题目。
题目描述的是:某部门有N名员工,每个员工有唯一的职级。需要从这些员工中选出3人组成开发小组,且必须满足以下两种排列方式之一:
其中i、j、k是员工的序号,且必须满足0 ≤ i < j < k < n(即保持原始顺序)。
输入包括:
输出为一个整数,表示符合条件的组合数量。
最直观的解法是三重循环暴力枚举所有可能的三元组,然后检查是否满足条件:
python复制def countTeams(level):
n = len(level)
count = 0
for i in range(n):
for j in range(i+1, n):
for k in range(j+1, n):
if (level[i] < level[j] < level[k]) or (level[i] > level[j] > level[k]):
count += 1
return count
这种方法的时间复杂度是O(n³),当n=6000时,计算量将达到6000³=216,000,000,000次操作,显然无法在合理时间内完成。
更高效的解法是固定中间元素,统计其左右两侧满足条件的元素数量。具体来说:
对于每个员工j(1 ≤ j ≤ n-2):
然后,该员工作为中间元素可以组成:
将所有员工的这两种情况相加,就是最终结果。
初始化四个数组:
填充leftLess和leftGreater:
填充rightLess和rightGreater:
计算结果:
java复制public int countTeams(int[] level) {
int n = level.length;
int[] leftLess = new int[n];
int[] leftGreater = new int[n];
int[] rightLess = new int[n];
int[] rightGreater = new int[n];
// 计算左侧比当前元素小和大的数量
for (int j = 1; j < n; j++) {
for (int i = 0; i < j; i++) {
if (level[i] < level[j]) {
leftLess[j]++;
} else if (level[i] > level[j]) {
leftGreater[j]++;
}
}
}
// 计算右侧比当前元素小和大的数量
for (int j = 0; j < n-1; j++) {
for (int k = j+1; k < n; k++) {
if (level[k] < level[j]) {
rightLess[j]++;
} else if (level[k] > level[j]) {
rightGreater[j]++;
}
}
}
// 计算结果
int result = 0;
for (int j = 1; j < n-1; j++) {
result += leftLess[j] * rightGreater[j] + leftGreater[j] * rightLess[j];
}
return result;
}
python复制def countTeams(level):
n = len(level)
left_less = [0] * n
left_greater = [0] * n
right_less = [0] * n
right_greater = [0] * n
# 计算左侧比当前元素小和大的数量
for j in range(1, n):
for i in range(j):
if level[i] < level[j]:
left_less[j] += 1
elif level[i] > level[j]:
left_greater[j] += 1
# 计算右侧比当前元素小和大的数量
for j in range(n-1):
for k in range(j+1, n):
if level[k] < level[j]:
right_less[j] += 1
elif level[k] > level[j]:
right_greater[j] += 1
# 计算结果
result = 0
for j in range(1, n-1):
result += left_less[j] * right_greater[j] + left_greater[j] * right_less[j]
return result
这种方法的时间复杂度是O(n²),因为有两层嵌套循环:
对于n=6000,计算量约为6000²=36,000,000次操作,这在现代计算机上可以在合理时间内完成。
对于更大的n值(虽然题目限制n≤6000),我们可以使用更高效的数据结构来优化统计过程:
java复制class FenwickTree {
private int[] tree;
public FenwickTree(int size) {
tree = new int[size + 1];
}
public void update(int index, int delta) {
while (index < tree.length) {
tree[index] += delta;
index += index & -index;
}
}
public int query(int index) {
int sum = 0;
while (index > 0) {
sum += tree[index];
index -= index & -index;
}
return sum;
}
}
public int countTeamsOptimized(int[] level) {
int n = level.length;
// 离散化处理
int[] sorted = Arrays.copyOf(level, n);
Arrays.sort(sorted);
Map<Integer, Integer> rank = new HashMap<>();
for (int i = 0; i < n; i++) {
rank.put(sorted[i], i + 1);
}
int[] leftLess = new int[n];
int[] leftGreater = new int[n];
FenwickTree ftLeft = new FenwickTree(n);
for (int j = 0; j < n; j++) {
int r = rank.get(level[j]);
leftLess[j] = ftLeft.query(r - 1);
leftGreater[j] = ftLeft.query(n) - ftLeft.query(r);
ftLeft.update(r, 1);
}
int[] rightLess = new int[n];
int[] rightGreater = new int[n];
FenwickTree ftRight = new FenwickTree(n);
for (int j = n - 1; j >= 0; j--) {
int r = rank.get(level[j]);
rightLess[j] = ftRight.query(r - 1);
rightGreater[j] = ftRight.query(n) - ftRight.query(r);
ftRight.update(r, 1);
}
int result = 0;
for (int j = 1; j < n - 1; j++) {
result += leftLess[j] * rightGreater[j] + leftGreater[j] * rightLess[j];
}
return result;
}
这种优化方法的时间复杂度是O(n log n),适合处理更大规模的数据。
我们可以观察到,rightLess和rightGreater数组实际上不需要全部存储,可以在遍历时直接计算:
python复制def countTeamsOptimized(level):
n = len(level)
left_less = [0] * n
left_greater = [0] * n
# 计算左侧比当前元素小和大的数量
for j in range(1, n):
for i in range(j):
if level[i] < level[j]:
left_less[j] += 1
elif level[i] > level[j]:
left_greater[j] += 1
result = 0
# 计算右侧比当前元素小和大的数量,并直接计算结果
for j in range(n-1):
right_less = 0
right_greater = 0
for k in range(j+1, n):
if level[k] < level[j]:
right_less += 1
elif level[k] > level[j]:
right_greater += 1
if 0 < j < n-1:
result += left_less[j] * right_greater + left_greater[j] * right_less
return result
这样可以将空间复杂度从O(n)降低到O(1),但时间复杂度保持不变。
python复制# 测试用例1
level = [1, 2, 3, 4]
print(countTeams(level)) # 输出: 4
# 测试用例2
level = [5, 4, 7]
print(countTeams(level)) # 输出: 0
# 测试用例3
level = [2, 5, 3, 4, 1]
print(countTeams(level)) # 输出: 3
# 解释:(2,5,4), (5,3,1), (5,4,1)
python复制# 最小输入(n=3,无解)
level = [1, 2, 1]
print(countTeams(level)) # 输出: 0
# 最小输入(n=3,有解)
level = [1, 2, 3]
print(countTeams(level)) # 输出: 1
# 所有元素相同
level = [5, 5, 5, 5]
print(countTeams(level)) # 输出: 0
# 严格递增序列
level = [1, 2, 3, 4, 5]
print(countTeams(level)) # 输出: 10 (C(5,3)=10)
# 严格递减序列
level = [5, 4, 3, 2, 1]
print(countTeams(level)) # 输出: 10
对于n=6000的随机数据,应确保算法在合理时间内完成:
python复制import random
level = [random.randint(1, 100000) for _ in range(6000)]
print(countTeams(level)) # 应在几秒内完成
cpp复制#include <vector>
using namespace std;
int countTeams(vector<int>& level) {
int n = level.size();
vector<int> leftLess(n, 0), leftGreater(n, 0);
vector<int> rightLess(n, 0), rightGreater(n, 0);
// 计算左侧统计
for (int j = 1; j < n; ++j) {
for (int i = 0; i < j; ++i) {
if (level[i] < level[j]) {
leftLess[j]++;
} else if (level[i] > level[j]) {
leftGreater[j]++;
}
}
}
// 计算右侧统计
for (int j = 0; j < n-1; ++j) {
for (int k = j+1; k < n; ++k) {
if (level[k] < level[j]) {
rightLess[j]++;
} else if (level[k] > level[j]) {
rightGreater[j]++;
}
}
}
// 计算结果
int result = 0;
for (int j = 1; j < n-1; ++j) {
result += leftLess[j] * rightGreater[j] + leftGreater[j] * rightLess[j];
}
return result;
}
javascript复制function countTeams(level) {
const n = level.length;
const leftLess = new Array(n).fill(0);
const leftGreater = new Array(n).fill(0);
const rightLess = new Array(n).fill(0);
const rightGreater = new Array(n).fill(0);
// 计算左侧统计
for (let j = 1; j < n; j++) {
for (let i = 0; i < j; i++) {
if (level[i] < level[j]) {
leftLess[j]++;
} else if (level[i] > level[j]) {
leftGreater[j]++;
}
}
}
// 计算右侧统计
for (let j = 0; j < n-1; j++) {
for (let k = j+1; k < n; k++) {
if (level[k] < level[j]) {
rightLess[j]++;
} else if (level[k] > level[j]) {
rightGreater[j]++;
}
}
}
// 计算结果
let result = 0;
for (let j = 1; j < n-1; j++) {
result += leftLess[j] * rightGreater[j] + leftGreater[j] * rightLess[j];
}
return result;
}
go复制func countTeams(level []int) int {
n := len(level)
leftLess := make([]int, n)
leftGreater := make([]int, n)
rightLess := make([]int, n)
rightGreater := make([]int, n)
// 计算左侧统计
for j := 1; j < n; j++ {
for i := 0; i < j; i++ {
if level[i] < level[j] {
leftLess[j]++
} else if level[i] > level[j] {
leftGreater[j]++
}
}
}
// 计算右侧统计
for j := 0; j < n-1; j++ {
for k := j+1; k < n; k++ {
if level[k] < level[j] {
rightLess[j]++
} else if level[k] > level[j] {
rightGreater[j]++
}
}
}
// 计算结果
result := 0
for j := 1; j < n-1; j++ {
result += leftLess[j] * rightGreater[j] + leftGreater[j] * rightLess[j]
}
return result
}
这类问题在实际中有多种应用:
在解决这类算法问题时,最重要的是理解问题本质,然后寻找优化的突破口。这道题的优化关键在于"固定中间元素"的思路,这种思维方式在很多算法问题中都有应用。