子陣列的最大異或和問題
作者:Grey
原文地址:
題目描述
陣列中所有數都異或起來的結果,叫做異或和。給定一個陣列 arr,其中可能有正、有負,有零,返回 arr 的最大子陣列異或和
題目連結見:牛客-子陣列的最大異或和
暴力解
列舉每個子陣列的異或和,抓取全域性最大值返回,整個演算法時間複雜度\(O(N^3)\),整個過程比較簡單,不贅述,基於這個暴力解法,可以有最佳化一些的演算法,就是利用字首異或和陣列,時間複雜度可以減少到\(O(N^2)\),思路如下
第一步
申請一個和原始陣列一樣長的字首異或和陣列
int[] eor = new int[arr.length];
其中eor[i]
表示原始陣列 0 位置到 i 位置的異或和是多少,實現程式碼如下:
eor[0] = arr[0];
for (int i = 1; i < n; i++) {
eor[i] = eor[i - 1] ^ arr[i];
}
有了 eor 陣列以後,對於任意 i 位置,0 到 i 區間的異或和就可以直接獲取到了,接下來是列舉陣列中任意兩個位置 i 和 j 區間的異或和,由於
i ~ j 之間的異或和等於 eor[j] ^ eor[i-1]
(i > 0),所以
任何兩個位置之間的異或和資訊可以透過如下程式碼求得,其中 max 是全域性異或和的最大值
for (int i = 1; i < n; i++) {
max = Math.max(max, eor[i]);
for (int j = i; j < n; j++) {
max = Math.max(max, eor[j] ^ eor[i - 1]);
}
}
完整程式碼如下
public static int maxEor1(int[] arr, int n) {
int[] eor = new int[arr.length];
int max = arr[0];
eor[0] = arr[0];
for (int i = 1; i < n; i++) {
eor[i] = eor[i - 1] ^ arr[i];
}
for (int i = 1; i < n; i++) {
max = Math.max(max, eor[i]);
for (int j = i; j < n; j++) {
max = Math.max(max, eor[j] ^ eor[i - 1]);
}
}
return max;
}
整個演算法複雜度是\(O(N^2)\),並不是最優解。
最優解
根據上述暴力解法,時間複雜度比較高的部分是:
當確定了 0 ~ i 位置的異或和以後,如何定位 0 ~ j 這個區間,使得 j ~ i 之間的異或和最大。
暴力解法使用的是遍歷的方式,而最優解,可以使用字首樹進行加速匹配,關於字首樹的內容,可以參考:字首樹的設計和實現
以陣列[11,1,15,10,13,4]
為例,我們把其字首異或和陣列轉換成二進位制,結果如下(其中eor[i..j]表示i~j的異或和)
eor[0..0] = 1011
eor[0..1] = 1010
eor[0..2] = 0101
eor[0..3] = 1111
eor[0..4] = 0010
eor[0..5] = 0110
把這些字首異或和加入字首樹,
接下來,對於任何一個eor[i]
(0~i的異或和)來說,進入字首樹以後,字首樹需要快速找到和其匹配的eor[j]
,使得i~j
之間的異或和最大,規則就是最高位(符號位)期待一樣,緊著高位要期待不一樣的值
例如:
eor[2] = 0101
eor[2] 期待和它符號位一樣為0的值,緊著高位(由於前面28都是0,所以不存在和它符號不一樣的,只看最後4位,
透過這個貪心,就可以在\(O(1)\)時間複雜度直接得到結果。
說明:如果期待遇到 0 可是字首樹沒有往 0 方向的路,那直接返回 1 即可,反之亦然。
完整程式碼如下
public static int maxEor(int[] arr, int n) {
int[] eor = new int[arr.length];
int max = 0;
eor[0] = arr[0];
for (int i = 1; i < n; i++) {
eor[i] = eor[i - 1] ^ arr[i];
}
Trie trie = new Trie(eor);
trie.add(eor[0]);
for (int i = 1; i < n; i++) {
max = Math.max(max, trie.get(eor[i]));
}
return max;
}
public static class Trie {
public Node head;
public Trie(int[] arr) {
head = new Node();
for (int eor : arr) {
add(eor);
}
}
public void add(int num) {
Node cur = head;
for (int bit = 31; bit >= 0; bit--) {
int i = ((num >>> bit) & 1);
if (cur.next[i] == null) {
cur.next[i] = new Node();
}
cur = cur.next[i];
}
}
public int get(int eor) {
int expect = 0;
Node cur = head;
for (int bit = 31; bit >= 0; bit--) {
// 符號位期待一樣的
// 非符號位期待相反的
int expectBit = bit == 31 ? ((eor >>> bit) & 1) : (eor >>> bit & 1 ^ 1);
if (cur.next[expectBit] != null) {
expect = ((expectBit << bit) | expect);
cur = cur.next[expectBit];
} else {
expectBit = (expectBit ^ 1);
cur = cur.next[expectBit];
expect = ((expectBit << bit) | expect);
}
}
return expect ^ eor;
}
}
public static class Node {
public Node[] next = new Node[2];
}
整個演算法時間複雜度\(O(N)\),最優解。