题目难度: 中等
原题链接
今天继续更新 Leetcode 的剑指 Offer(专项突击版)系列, 大家在公众号 算法精选 里回复
剑指offer2
就能看到该系列当前连载的所有文章了, 记得关注哦~
题目描述
给定一个整数数组 nums ,返回 nums[i] XOR nums[j] 的最大运算结果,其中 0 ≤ i ≤ j < n 。
示例 1:
- 输入:nums = [3,10,5,25,2,8]
- 输出:28
- 解释:最大运算结果是 5 XOR 25 = 28.
示例 2:
- 输入:nums = [0]
- 输出:0
示例 3:
- 输入:nums = [2,4]
- 输出:6
示例 4:
- 输入:nums = [8,10,2]
- 输出:10
示例 5:
- 输入:nums = [14,70,53,83,49,91,36,80,92,51,66,70]
- 输出:127
提示:
- 1 <= nums.length <= 2 * 10^5
- 0 <= nums[i] <= 2^31 - 1
- 进阶:你可以在 O(n) 的时间解决这个问题吗?
题目思考
- 如何优化时间复杂度?
解决方案
思路
- 分析题目, 一个最直接的思路就是两重循环求每个数字对的异或值, 从而得到最大值
- 不过这样做的时间复杂度达到了 O(N^2), 按照题目规模肯定会超时, 如何优化呢?
- 假设数组的最大异或值
x=ai^aj
, 那么一定有pk(x)=pk(ai)^pk(aj)
(pk(n)表示 n 的前 k 位) - 根据异或结合律, 则有
pk(ai)=pk(x)^pk(aj)
, 我们可以利用集合判断当前 pk(x)的最低位应该是 1 还是 0, 具体步骤如下:- 令 res 记录最大异或值的当前前缀部分 pk(x), 初始化为 0
- 然后按照前缀长度从小到大的顺序遍历
- 对于前缀长度 k, 遍历所有数字, 右移 k 位求出对应的前缀, 并加入集合中
- 接下来假设最大异或值的这一位是 1, 也即 res 左移一位并加 1
- 再遍历所有数字, 将 res 与对应数字的前缀异或
- 如果某个异或的结果在当前前缀集合里, 则说明该位真的可以为 1, 即满足
pk(ai)=pk(x)^pk(aj)
, 跳出循环 - 如果遍历结束仍未找到, 则说明不存在某个异或结果在前缀集合中, 该位只能为 0, 将 res 减去 1
- 最后, 遍历完所有前缀长度后的 res 即为所求
- 下面代码中有详细的注释, 方便大家理解
复杂度
- 时间复杂度 O(N): 外层循环次数是常数, 内层循环每次只需要遍历整个数组一遍
- 空间复杂度 O(N): 需要额外的集合存储每次遍历的所有数字前缀
代码
class Solution:def findMaximumXOR(self, nums: List[int]) -> int:res = 0for k in range(32)[::-1]:v = set()for x in nums:# 将所有数字的pk前缀加入集合v.add(x >> k)# 先假设当前位可以为1res = (res << 1) + 1for x in nums:if res ^ (x >> k) in v:# res与该前缀的异或结果已经在集合中了, 说明最终最大异或值的这一位是1breakelse:# 异或结果不在前缀集合中, 这一位只能为0res -= 1return res
大家可以在下面这些地方找到我~😊
我的 GitHub
我的 Leetcode
我的 CSDN
我的知乎专栏
我的头条号
我的牛客网博客
我的公众号: 算法精选, 欢迎大家扫码关注~😊