DeepSeek LeetCode 3845. 最大子数组异或值 C语言实现
问题描述
给定一个非负整数数组 nums 和一个整数 k,需要选择一个连续非空子数组,使得该子数组内最大值与最小值的差不超过 k。子数组的"值"定义为其中所有元素的按位异或(XOR)。要求返回所有合法子数组中,最大的异或值。
```
输入: nums = [5,4,5,6], k = 2
输出: 7
解释: 选择子数组 [5,4,5,6],最大值6 - 最小值4 = 2 ≤ k,异或值 4^5^6 = 7
```
约束:nums.length ≤ 4×10^4,nums[i] < 2^15,暴力枚举所有子数组(O(n²))不可行。
---
算法思路
本题结合了三个经典技巧:
1. 前缀异或(Prefix XOR):子数组 [l, r] 的异或值 = prefix[r+1] ^ prefix[l],将子数组异或问题转化为"两个前缀值的异或最大值"问题。
2. 01字典树(Binary Trie):将所有前缀异或值按二进制位插入 Trie,查询时贪心选择相反的位,即可快速找到与当前值异或最大的前缀值。
3. 滑动窗口 + 单调队列:维护窗口内最大值和最小值,保证 max - min ≤ k。
整体流程:用右指针扩展窗口,用两个单调队列维护窗口最值。当窗口不满足 max - min ≤ k 时,移动左指针缩小窗口。每次窗口合法时,将对应的前缀异或值加入 Trie,查询当前前缀能得到的最大异或值并更新答案。
---
C语言完整实现
```c
#define MAX_NODES 600000 // n ≤ 40000,每个数15位,节点数 ≤ n * 15 + 1
typedef struct TrieNode {
struct TrieNode* child[2];
int count; // 该节点被多少前缀值经过(用于删除)
} TrieNode;
TrieNode pool[MAX_NODES];
int pool_idx;
TrieNode* newNode() {
TrieNode* node = &pool[pool_idx++];
node->child[0] = node->child[1] = NULL;
node->count = 0;
return node;
}
// 插入或删除一个前缀异或值(delta = 1 插入,-1 删除)
void updateTrie(TrieNode* root, int val, int delta) {
TrieNode* cur = root;
for (int bit = 14; bit >= 0; bit--) { // 2^15,所以15位(14~0)
int b = (val >> bit) & 1;
if (!cur->child[b]) cur->child[b] = newNode();
cur = cur->child[b];
cur->count += delta;
}
}
// 查询与 val 异或最大的值
int queryMaxXor(TrieNode* root, int val) {
TrieNode* cur = root;
int res = 0;
for (int bit = 14; bit >= 0; bit--) {
int b = (val >> bit) & 1;
int opposite = b ^ 1;
if (cur->child[opposite] && cur->child[opposite]->count > 0) {
res |= (1 << bit);
cur = cur->child[opposite];
} else {
cur = cur->child[b];
}
}
return res;
}
// 单调队列(存储下标)
int maxQ[40005], minQ[40005];
int maxHead, maxTail, minHead, minTail;
int maxXor(int* nums, int numsSize, int k) {
pool_idx = 0;
TrieNode* root = newNode();
maxHead = maxTail = minHead = minTail = 0;
int* prefix = (int*)malloc((numsSize + 1) * sizeof(int));
prefix[0] = 0;
for (int i = 0; i < numsSize; i++) {
prefix[i + 1] = prefix[i] ^ nums[i];
}
int left = 0;
int ans = 0;
// 先插入 prefix[0](对应空前缀,表示从数组开头开始的子数组)
updateTrie(root, prefix[0], 1);
for (int right = 0; right < numsSize; right++) {
// 维护最大值单调队列(递减)
while (maxHead < maxTail && nums[maxQ[maxTail - 1]] <= nums[right]) maxTail--;
maxQ[maxTail++] = right;
// 维护最小值单调队列(递增)
while (minHead < minTail && nums[minQ[minTail - 1]] >= nums[right]) minTail--;
minQ[minTail++] = right;
// 如果窗口不满足 max - min ≤ k,移动左指针
while (maxHead < maxTail && minHead < minTail &&
nums[maxQ[maxHead]] - nums[minQ[minHead]] > k) {
// 从 Trie 中移除 prefix[left]
updateTrie(root, prefix[left], -1);
left++;
// 移除队列中已经滑出窗口的下标
while (maxHead < maxTail && maxQ[maxHead] < left) maxHead++;
while (minHead < minTail && minQ[minHead] < left) minHead++;
}
// 查询当前前缀 prefix[right+1] 能与 Trie 中已有前缀产生的最大异或值
int curXor = queryMaxXor(root, prefix[right + 1]);
if (curXor > ans) ans = curXor;
// 将当前前缀插入 Trie
updateTrie(root, prefix[right + 1], 1);
}
free(prefix);
return ans;
}
```
---
代码说明
· Trie 节点池:用静态数组 pool 预分配内存,避免频繁 malloc,提高效率。
· count 字段:记录经过该节点的前缀值数量,支持删除操作(窗口滑动时移出左端前缀)。
· 15位遍历:因为 nums[i] < 2^15,异或值也在15位范围内,从高位(bit 14)到低位贪心。
· 单调队列:maxQ 维护窗口最大值下标(递减),minQ 维护最小值下标(递增)。窗口不满足条件时移动 left,并从 Trie 中移除对应的前缀值。
· 时间复杂度:O(n × 15),空间复杂度 O(n × 15)。

更多推荐




所有评论(0)