🔗 🟡 2491. Divide Players Into Teams of Equal Skill 1323

tags: Weekly Contest 322 陣列(Array) 雙指標(Two Pointers) 排序(Sorting) 雜湊表(Hash Table)

題意

給定一個正整數陣列 skillskill,其長度 nn偶數,其中 skill[i]skill[i] 表示第 ii 個玩家的技能。將玩家分成 n/2n / 2 支隊伍,每個隊伍的大小為 22,使得每個隊伍的總技能相等

一個隊伍的化學反應等於該隊伍中兩個玩家技能的乘積

返回所有隊伍的化學反應的總和,或者如果無法將玩家分成使每個隊伍總技能相等的隊伍,則返回 1-1

約束條件

  • n=skill.lengthn = \text{skill.length}nn 為偶數
  • 2n1052 \leq n \leq 10^5
  • 1skill[i]10001 \leq \text{skill[i]} \leq 1000

思路:雙指標 + 排序 / 雜湊表

方法一:雙指標(Two Pointers) + 排序(Sorting)

若要每個隊伍的總技能相等,則對於技能最小的玩家,其配對的隊友技能值 必定 是最大的,反之亦然。

可以使用矛盾法證明,證明如下:

Proof

LL 為最小的技能值、HH 為最大的技能值,targettarget 為每個隊伍的總技能值。

假設 LL 不能與 HH 配對,則 LL 只能與某個 XX 配對,其中 LXHL \leq X \leq HHH 只能與某個 YY 配對,其中 LYHL \leq Y \leq H

由於 LLXX 配對、HHYY 配對,故 L+X=H+Y=targetL + X = H + Y = target。得 X=targetLX = target - LY=targetHY = target - H

因為 XHX \leq H,故 targetLHtarget - L \leq H,即 targetH+Ltarget \leq H + L
因為 YLY \geq L,故 targetHLtarget - H \geq L,即 targetH+Ltarget \geq H + L

因此,targettarget 必定等於 H+LH + L,這與假設矛盾。故 LL 必定與 HH 配對。

而配對完 LLHH 後,剩下的技能值陣列中,次小值 L1L_1 也必定和次大值 H1H_1 的配對,證明方式和上述相同。

因此,我們可以先將技能陣列排序,再使用雙指標法,一個指標指向最小的技能值,另一個指標指向最大的技能值,逐步向內收縮,檢查是否能配對成功。

target=skill[0]+skill[n1]target = skill[0] + skill[n-1],表示每個隊伍的總技能值。對於要檢查的每一對玩家,若其技能和不等於 targettarget,則無法使每個隊伍的總技能相等,返回 1-1;否則,計算該隊伍的化學反應,並累加到答案中。

複雜度分析

  • 時間複雜度:O(nlogn)\mathcal{O}(n \log n)
    • 排序的時間複雜度為 O(nlogn)O(n \log n)
    • 雙指標遍歷的時間複雜度為 O(n)O(n)
  • 空間複雜度:O(n)\mathcal{O}(n)O(logn)\mathcal{O}(\log n),取決於排序演算法所需的空間複雜度。
    • 在 Python 中,sort() 是基於 Timsort 的,其空間複雜度為 O(n)\mathcal{O}(n)
    • 而在 C++ 中,sort() 是 QuickSort、HeapSort 和 Insertion Sort 的混合實現,其空間複雜度為 O(logn)\mathcal{O}(\log n)
1
2
3
4
5
6
7
8
9
10
11
class Solution:
def dividePlayers(self, skill: List[int]) -> int:
n = len(skill)
skill.sort()
target = skill[0] + skill[-1]
ans = 0
for i in range(n // 2):
if skill[i] + skill[n - i - 1] != target:
return -1
ans += skill[i] * skill[n - i - 1]
return ans
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public:
long long dividePlayers(vector<int>& skill) {
int n = skill.size();
sort(skill.begin(), skill.end());
int target = skill[0] + skill[n - 1];
long long ans = 0;
for (int i = 0; i < n / 2; i++) {
if (skill[i] + skill[n - i - 1] != target) return -1;
ans += (long long) skill[i] * skill[n - i - 1];
}
return ans;
}
};

方法二:雜湊表(Hash Table)

在方法一中我們假定每一組的技能和 targettargetL+HL + H,而實際上 targettarget 可以被計算得出。令 ss 為所有技能的總和,m=n2m = \frac{n}{2} 為組別的數量,則 target=smtarget = \frac{s}{m}

在計算出 targettarget 後,對於技能為 xx 的玩家,其配對的隊友技能值必定為 targetxtarget - x。因此,我們可以使用雜湊表來計數每個技能值的出現次數,並且確保可以組成符合條件的隊伍。

由於 skillskill 中只存在整數值,故 targettarget 也必為整數。若 targettarget 不是整數,也就是 ss 不能被 mm 整除,則表示無法組成符合條件的隊伍,返回 1-1

接著,遍歷雜湊表,對於每個技能值 kk,檢查是否存在 targetktarget - k,且兩者的出現次數相等。如果不相等,則無法形成隊伍,返回 1-1

最後,計算化學反應的總和。對於每個技能值 kk,其貢獻為 k(targetk)vk * (target - k) * v,其中 vv 是該技能值的出現次數。由於每對隊伍的計算都被計算了兩次,因此最後結果需要除以 22

複雜度分析

  • 時間複雜度:O(n)\mathcal{O}(n)
    • 計算總和的時間複雜度為 O(n)O(n)
    • 遍歷雜湊表的時間複雜度為 O(n)O(n)
  • 空間複雜度:O(n)\mathcal{O}(n)
    • 使用雜湊表來儲存每個技能值的出現次數,因此空間複雜度為 O(n)O(n)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution:
def dividePlayers(self, skill: List[int]) -> int:
m = len(skill) // 2
s = sum(skill)
if s % m != 0:
return -1
target = s // m
cnt = Counter(skill)
ans = 0
for k, v in cnt.items():
if cnt[target - k] != v:
return -1
ans += k * (target - k) * v
return ans // 2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
long long dividePlayers(vector<int>& skill) {
int n = skill.size(), m = n / 2;
int s = accumulate(skill.begin(), skill.end(), 0);
if (s % m != 0) return -1;
int target = s / m;
unordered_map<int, int> cnt;
for (int x : skill) cnt[x]++;
long long ans = 0;
for (auto [k, v] : cnt) {
if (cnt[target - k] != v) return -1;
ans += (long long) k * (target - k) * v;
}
return ans / 2;
}
};

寫在最後

Cover photo is remixed from @吃肥皂泡泡, thanks for their work!

直覺想到的是方法二,但方法二的分數應該不只 1323 分 XD