3514. Number of Unique XOR Triplets II
題目 / Problem
中文: 給定一個整數陣列 nums。一個「XOR 三元組」定義為 nums[i] XOR nums[j] XOR nums[k],其中索引滿足 i <= j <= k(索引可以重複)。請回傳所有可能三元組所產生的不同 XOR 值的數量。
English: You are given an integer array nums. A XOR triplet is nums[i] XOR nums[j] XOR nums[k] where i <= j <= k (indices may repeat). Return the number of unique XOR triplet values over all valid (i, j, k).
限制 / Constraints:
- 1 <= nums.length <= 1500
- 1 <= nums[i] <= 1500
範例 / Example: nums = [1,3]
- (0,0,0) → 1^1^1 = 1
- (0,0,1) → 1^1^3 = 3
- (0,1,1) → 1^3^3 = 1
- (1,1,1) → 3^3^3 = 3
不同的值是 {1, 3},所以答案是 2 / The unique values are {1, 3}, so the answer is 2.
名詞解釋 / Glossary
- XOR(互斥或 / exclusive or,
^) — 逐位元比較兩個數,位元不同得1、相同得0。它有兩個關鍵性質:交換律(a^b == b^a)與a^a == 0。A bitwise operation on two numbers: each bit is1when the two input bits differ. It is commutative and satisfiesa^a == 0. - 多重集合(multiset) — 允許重複元素的集合。因為
i <= j <= k只是要求索引由小到大排列,實際上我們是在挑「任意 3 個可重複的元素」。A set that allows repeats; thei<=j<=krule just means "pick any 3 elements, repeats allowed." - 集合去重 / set (dedup) — 一種只保留不同元素的容器。我們用它避免重複計算相同的值。A container that keeps only distinct values, so we never count a value twice.
- 布林標記陣列 / boolean marker array — 一個以「值」當索引的 0/1 陣列,
arr[v]=1表示值v出現過。因為所有值都很小(< 2048),我們可以用它取代雜湊表,速度更快。An array indexed by value;arr[v]=1means valuevhas appeared. Since all values are small (< 2048), this replaces a hash set and is faster. - 位元寬度 / bit width —
nums[i] <= 1500 < 2048 = 2^11,所以每個數最多 11 個位元;三個這種數 XOR 後仍< 2048。Each value fits in 11 bits, so any XOR of them stays below 2048.
思路
中文: 最直接的暴力法是三層迴圈枚舉所有 (i, j, k),計算 nums[i]^nums[j]^nums[k] 並丟進集合,這是 O(n³)。當 n = 1500 時約 33 億次運算,會超時。關鍵觀察是:條件 i <= j <= k 其實只是「把三個索引由小到大排序」,所以我們挑的是任意三個可重複的元素(一個大小為 3 的多重集合)。既然 XOR 有交換律,元素的順序不重要,我們甚至不需要在意原本的索引——只要在意有哪些不同的數值。第二個關鍵是值域很小:nums[i] <= 1500 < 2048,而 XOR 不會讓位元變多,所以任何三元組的結果也 < 2048。於是做法變成兩步:先算出所有「兩數 XOR」的集合 P = {a^b}(用一個大小 2048 的布林陣列標記),再把 P 裡的每個值和每個不同的 nums 值再 XOR 一次,得到三元組值的集合 T = {p^c}。最後數 T 裡有幾個被標記的值就是答案。先去重數值可以把外層迴圈從 n 降到「不同值的個數 m」(最多 1500,但常常更少),而值域上限 2048 讓內層迴圈有固定上界。
English: The naive approach loops over all (i, j, k) in O(n³), which is ~3.3 billion operations at n = 1500 — too slow. The key insight is that i <= j <= k merely says "sort the three chosen indices," so we are really picking any 3 elements with repetition (a size-3 multiset). Because XOR is commutative, order doesn't matter and neither do the original indices — only the distinct values matter. The second key fact is that the value range is tiny: nums[i] <= 1500 < 2048, and XOR never adds high bits, so every triplet result is also < 2048. This turns the problem into two passes. First build the set of all pairwise XORs P = {a^b} using a size-2048 boolean marker array. Then XOR each value in P with each distinct nums value to get the triplet set T = {p^c}. The answer is the number of marked entries in T. Deduplicating values first shrinks the loops from n to the number of distinct values m, and the fixed 2048 bound caps the inner work.
逐步走查 / Walkthrough
輸入 / Input: nums = [1, 3]
| 步驟 / Step | 動作 / Action | 狀態 / State |
|---|---|---|
| 1 | 標記出現過的值 / mark present values | present[1]=1, present[3]=1 |
| 2 | 收集不同值 / collect distinct | distinct = [1, 3], m = 2 |
| 3 | 兩數 XOR:1^1 / pair xor |
0 → pair[0]=1 |
| 4 | 兩數 XOR:1^3 |
2 → pair[2]=1 |
| 5 | 兩數 XOR:3^3 |
0 → pair[0]=1(已標記 / already set) |
| — | 目前配對集合 / pair set so far | P = {0, 2} |
| 6 | 三元組:p=0 ^ c=1 |
1 → triple[1]=1 |
| 7 | 三元組:p=0 ^ c=3 |
3 → triple[3]=1 |
| 8 | 三元組:p=2 ^ c=1 |
3 → triple[3]=1(重複 / duplicate) |
| 9 | 三元組:p=2 ^ c=3 |
1 → triple[1]=1(重複 / duplicate) |
| — | 三元組集合 / triple set | T = {1, 3} |
| 10 | 計數 / count marked | 2 ✅ |
Solution — C
// 演算法 / Algorithm:
// 1) 去重,得到不同的數值 distinct[] / dedup to distinct values.
// 2) 對每對不同值做 XOR,記錄所有「兩數 XOR」結果 / all pairwise XORs.
// 3) 再把每個兩數結果與每個不同值 XOR,記錄「三元組 XOR」/ all triplet XORs.
// 4) 數三元組集合中被標記的值 / count marked triplet values.
// 值域上限:nums[i] <= 1500 < 2048,三個值 XOR 後仍 < 2048。
// Value bound: nums[i] < 2048, so any XOR of them stays < 2048.
#define LIM 2048
int uniqueXorTriplets(int* nums, int numsSize) {
// present[v]=1 表示數值 v 在 nums 裡出現過 / v appears in nums.
// 用值當索引的布林陣列,比雜湊表更快 / value-indexed boolean array, faster than a hash set.
char present[LIM] = {0}; // {0} 把整個陣列初始化為 0 / initialize all to 0.
for (int i = 0; i < numsSize; i++) // 掃過每個元素 / scan each element.
present[nums[i]] = 1; // 標記它出現過 / mark it as seen.
// 把出現過的值收集成一個緊湊的清單 / collect seen values into a compact list.
int distinct[LIM]; // 最多 2048 個不同值 / at most 2048 distinct values.
int m = 0; // m 是不同值的個數 / m = count of distinct values.
for (int v = 0; v < LIM; v++) // 依序檢查每個可能值 / check every possible value.
if (present[v]) distinct[m++] = v; // 若出現過就加入清單 / append if seen.
// pair[x]=1 表示某對不同值的 XOR 等於 x / x is achievable as a^b.
char pair[LIM] = {0};
for (int a = 0; a < m; a++) // 選第一個值 distinct[a] / pick first value.
for (int b = a; b < m; b++) // 選第二個值(b 從 a 開始,允許 a==b)/ second value, b>=a allows repeat.
pair[distinct[a] ^ distinct[b]] = 1; // ^ 是位元 XOR,標記這個配對結果 / mark this pairwise XOR.
// triple[x]=1 表示某個三元組的 XOR 等於 x / x is achievable as a^b^c.
char triple[LIM] = {0};
for (int p = 0; p < LIM; p++) { // 掃過所有可能的「兩數 XOR」值 / scan all pair values.
if (!pair[p]) continue; // 跳過沒被標記的值 / skip values never produced.
for (int c = 0; c < m; c++) // 再 XOR 上每個不同值 / XOR with each distinct value.
triple[p ^ distinct[c]] = 1; // 標記三元組結果 / mark this triplet XOR.
}
// 數出被標記的三元組值有幾個 / count how many triplet values are marked.
int count = 0;
for (int x = 0; x < LIM; x++) // 掃過整個值域 / scan the whole range.
if (triple[x]) count++; // 每個被標記的值代表一個不同答案 / each marked value = one unique result.
return count; // 這就是不同三元組 XOR 值的數量 / the answer.
}
Solution — C++
// 演算法與 C 版相同:去重 → 兩數 XOR 集合 → 三元組 XOR 集合 → 計數。
// Same algorithm as the C version: dedup → pairwise XOR set → triplet XOR set → count.
#include <vector>
#include <bitset>
using namespace std;
class Solution {
public:
int uniqueXorTriplets(vector<int>& nums) {
const int LIM = 2048; // 值域上限(2^11),因為值 < 2048 / value bound, since values < 2048.
// bitset<LIM> 是固定大小的位元集合,每個位置只佔 1 bit,且自動歸零。
// bitset<LIM> is a fixed-size bit container; each slot is 1 bit and starts as 0.
bitset<LIM> present; // present[v]=1 表示 v 出現過 / v appears in nums.
for (int v : nums) // range-for:依序取出每個元素 / iterate each element.
present[v] = 1; // 標記出現 / mark as seen.
// 把出現過的值收集成緊湊清單,之後迴圈只跑不同值 / compact list of distinct values.
vector<int> distinct; // vector 是可自動增長的動態陣列 / a growable dynamic array.
for (int v = 0; v < LIM; v++)
if (present[v]) distinct.push_back(v); // push_back 在尾端加入一個元素 / append at the end.
// pair[x]=1 表示 x 可由某對不同值 XOR 得到 / x achievable as a^b.
bitset<LIM> pair;
for (size_t a = 0; a < distinct.size(); a++) // 第一個值 / first value.
for (size_t b = a; b < distinct.size(); b++) // 第二個值,b>=a 允許重複 / second value, repeats allowed.
pair[distinct[a] ^ distinct[b]] = 1; // ^ 為位元 XOR / bitwise XOR, mark result.
// triple[x]=1 表示 x 可由某三元組 XOR 得到 / x achievable as a^b^c.
bitset<LIM> triple;
for (int p = 0; p < LIM; p++) { // 掃過所有可能的兩數 XOR 值 / scan all pair values.
if (!pair[p]) continue; // 沒被標記就跳過 / skip if never produced.
for (int c : distinct) // 再 XOR 上每個不同值 / XOR with each distinct value.
triple[p ^ c] = 1; // 標記三元組結果 / mark this triplet XOR.
}
// bitset 的 count() 直接回傳有幾個位元是 1 / count() returns the number of set bits.
return (int)triple.count(); // 即不同三元組 XOR 值的數量 / the answer.
}
};
複雜度 / Complexity
- Time:
O(m² + V·m),其中m是不同值的個數(≤ 1500),V = 2048是值域大小。兩數 XOR 的雙層迴圈是O(m²)(約 100 萬次),三元組迴圈最多V·m次。整體遠優於暴力的O(n³)。Heremis the number of distinct values andV = 2048is the value range; the pairwise loop dominates atO(m²), far better than the naiveO(n³). - Space:
O(V)— 三個大小 2048 的布林/位元陣列加上不同值清單,都是常數等級的固定空間。Three size-2048 marker arrays plus the distinct-value list — effectively constant extra space.
Pitfalls & Edge Cases
- 值域大小要抓對 / Get the array bound right —
nums[i] <= 1500,但答案值域是 XOR 結果,最大到2047,所以陣列要開到2048而非1501。開太小會越界寫入(undefined behavior)。The result range goes up to2047, so size the arrays to2048, not1501. - 索引可以重複 / Indices may repeat —
i <= j <= k允許i==j==k,因此配對迴圈用b從a(含)開始,別寫成a+1,否則會漏掉像a^a=0這種情況。Start the inner loop atb = a, nota+1, or you miss cases likea^a. - 一定要先去重 / Deduplicate first — 不去重雖然答案仍正確,但迴圈會變成
O(n²);當有大量重複值時去重能大幅加速。Correctness is unaffected, but skipping dedup makes the loopsO(n²)and slow on repeat-heavy inputs. - 單一元素 / Single element (
n == 1) — 唯一三元組是nums[0]^nums[0]^nums[0] = nums[0],答案為1;本解法自然涵蓋(P={0},T={nums[0]})。The lone triplet yieldsnums[0], answer1; the code handles it naturally. - 不要數配對集合 / Don't count the pair set by mistake — 最終答案來自
triple,不是pair;混淆兩者會回傳錯誤數字。The answer comes fromtriple, notpair— mixing them up returns the wrong count.