題意
給定一個正整數陣列 s k i l l skill s ki ll ,其長度 n n n 為偶數 ,其中 s k i l l [ i ] skill[i] s ki ll [ i ] 表示第 i i i 個玩家的技能。將玩家分成 n / 2 n / 2 n /2 支隊伍,每個隊伍的大小為 2 2 2 ,使得每個隊伍的總技能相等 。
一個隊伍的化學反應 等於該隊伍中兩個玩家技能的乘積 。
返回所有隊伍的化學反應 的總和,或者如果無法將玩家分成使每個隊伍總技能相等的隊伍,則返回 − 1 -1 − 1 。
約束條件
n = skill.length n = \text{skill.length} n = skill.length 且 n n n 為偶數
2 ≤ n ≤ 1 0 5 2 \leq n \leq 10^5 2 ≤ n ≤ 1 0 5
1 ≤ skill[i] ≤ 1000 1 \leq \text{skill[i]} \leq 1000 1 ≤ skill[i] ≤ 1000
思路:雙指標 + 排序 / 雜湊表
方法一:雙指標(Two Pointers) + 排序(Sorting)
若要每個隊伍的總技能相等,則對於技能最小的玩家,其配對的隊友技能值 必定 是最大的,反之亦然。
可以使用矛盾法證明,證明如下:
Proof 令 L L L 為最小的技能值、H H H 為最大的技能值,t a r g e t target t a r g e t 為每個隊伍的總技能值。
假設 L L L 不能與 H H H 配對,則 L L L 只能與某個 X X X 配對,其中 L ≤ X ≤ H L \leq X \leq H L ≤ X ≤ H ;H H H 只能與某個 Y Y Y 配對,其中 L ≤ Y ≤ H L \leq Y \leq H L ≤ Y ≤ H 。
由於 L L L 和 X X X 配對、H H H 和 Y Y Y 配對,故 L + X = H + Y = t a r g e t L + X = H + Y = target L + X = H + Y = t a r g e t 。得 X = t a r g e t − L X = target - L X = t a r g e t − L 且 Y = t a r g e t − H Y = target - H Y = t a r g e t − H 。
因為 X ≤ H X \leq H X ≤ H ,故 t a r g e t − L ≤ H target - L \leq H t a r g e t − L ≤ H ,即 t a r g e t ≤ H + L target \leq H + L t a r g e t ≤ H + L ;
因為 Y ≥ L Y \geq L Y ≥ L ,故 t a r g e t − H ≥ L target - H \geq L t a r g e t − H ≥ L ,即 t a r g e t ≥ H + L target \geq H + L t a r g e t ≥ H + L 。
因此,t a r g e t target t a r g e t 必定等於 H + L H + L H + L ,這與假設矛盾。故 L L L 必定與 H H H 配對。
而配對完 L L L 和 H H H 後,剩下的技能值陣列中,次小值 L 1 L_1 L 1 也必定和次大值 H 1 H_1 H 1 的配對,證明方式和上述相同。
因此,我們可以先將技能陣列排序,再使用雙指標法,一個指標指向最小的技能值,另一個指標指向最大的技能值,逐步向內收縮,檢查是否能配對成功。
令 t a r g e t = s k i l l [ 0 ] + s k i l l [ n − 1 ] target = skill[0] + skill[n-1] t a r g e t = s ki ll [ 0 ] + s ki ll [ n − 1 ] ,表示每個隊伍的總技能值。對於要檢查的每一對玩家,若其技能和不等於 t a r g e t target t a r g e t ,則無法使每個隊伍的總技能相等,返回 − 1 -1 − 1 ;否則,計算該隊伍的化學反應,並累加到答案中。
複雜度分析
時間複雜度:O ( n log n ) \mathcal{O}(n \log n) O ( n log n )
排序的時間複雜度為 O ( n log n ) O(n \log n) O ( n log n )
雙指標遍歷的時間複雜度為 O ( n ) O(n) O ( n )
空間複雜度:O ( n ) \mathcal{O}(n) O ( n ) 或 O ( log n ) \mathcal{O}(\log n) O ( log n ) ,取決於排序演算法所需的空間複雜度。
在 Python 中,sort()
是基於 Timsort 的,其空間複雜度為 O ( n ) \mathcal{O}(n) O ( n )
而在 C++ 中,sort()
是 QuickSort、HeapSort 和 Insertion Sort 的混合實現,其空間複雜度為 O ( log n ) \mathcal{O}(\log n) 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)
在方法一中我們假定每一組的技能和 t a r g e t target t a r g e t 是 L + H L + H L + H ,而實際上 t a r g e t target t a r g e t 可以被計算得出。令 s s s 為所有技能的總和,m = n 2 m = \frac{n}{2} m = 2 n 為組別的數量,則 t a r g e t = s m target = \frac{s}{m} t a r g e t = m s 。
在計算出 t a r g e t target t a r g e t 後,對於技能為 x x x 的玩家,其配對的隊友技能值必定為 t a r g e t − x target - x t a r g e t − x 。因此,我們可以使用雜湊表來計數每個技能值的出現次數,並且確保可以組成符合條件的隊伍。
由於 s k i l l skill s ki ll 中只存在整數值,故 t a r g e t target t a r g e t 也必為整數。若 t a r g e t target t a r g e t 不是整數,也就是 s s s 不能被 m m m 整除,則表示無法組成符合條件的隊伍,返回 − 1 -1 − 1 。
接著,遍歷雜湊表,對於每個技能值 k k k ,檢查是否存在 t a r g e t − k target - k t a r g e t − k ,且兩者的出現次數相等。如果不相等,則無法形成隊伍,返回 − 1 -1 − 1 。
最後,計算化學反應的總和。對於每個技能值 k k k ,其貢獻為 k ∗ ( t a r g e t − k ) ∗ v k * (target - k) * v k ∗ ( t a r g e t − k ) ∗ v ,其中 v v v 是該技能值的出現次數。由於每對隊伍的計算都被計算了兩次,因此最後結果需要除以 2 2 2 。
複雜度分析
時間複雜度:O ( n ) \mathcal{O}(n) O ( n )
計算總和的時間複雜度為 O ( n ) O(n) O ( n )
遍歷雜湊表的時間複雜度為 O ( n ) O(n) O ( n )
空間複雜度:O ( n ) \mathcal{O}(n) O ( n )
使用雜湊表來儲存每個技能值的出現次數,因此空間複雜度為 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