算法要求
给你一个下标从 0 开始长度为 n
的整数数组 nums
和一个整数 k
。每一次操作中,你可以选择一个数并将它乘 2
。
你最多可以进行 k
次操作,请你返回 nums[0] | nums[1] | ... | nums[n - 1]
的最大值。
a | b
表示两个整数 a
和 b
的 按位或 运算。
示例 1:
输入:nums = [12,9], k = 1
输出:30
解释:如果我们对下标为 1 的元素进行操作,新的数组为 [12,18] 。此时得到最优答案为 12 和 18 的按位或运算的结果,也就是 30 。
示例 2:
输入:nums = [8,1,2], k = 2
输出:35
解释:如果我们对下标 0 处的元素进行操作,得到新数组 [32,1,2] 。此时得到最优答案为 32|1|2 = 35 。
提示:
1 <= nums.length <= 105
1 <= nums[i] <= 109
1 <= k <= 15
算法解析
方法一:前后缀分解
核心思路
要让最终的或值最大,应尽可能让某个元素通过左移
k
次(即乘以2^k
)来增加其二进制长度。例如,将x
左移k
次得到x << k
,其二进制长度增加k
位。
关键步骤
预处理后缀或数组:
suf[i]
表示数组nums[i+1..n-1]
的或值。这样,当处理nums[i]
时,右边元素的或值可以直接通过suf[i]
获取。遍历计算前缀或:
维护一个变量pre
,表示当前遍历到的元素左边所有元素的或值。对于每个元素nums[i]
,计算pre | (nums[i] << k) | suf[i]
,并更新pre
。
示例代码
class Solution:
def maximumOr(self, nums: List[int], k: int) -> int:
n = len(nums)
suf = [0] * n
for i in range(n-2, -1, -1):
suf[i] = suf[i+1] | nums[i+1]
ans = pre = 0
for x, suf_or in zip(nums, suf):
ans = max(ans, pre | (x << k) | suf_or)
pre |= x
return ans
方法二:空间优化
核心思路
利用以下数学观察:
所有元素的或值
allOr
:直接计算数组中所有元素的或值。固定位
fixed
:记录那些在多个元素中出现的位。这些位在去掉任意一个元素后,仍会保留在或值中。
关键步骤
计算
allOr
和fixed
:allOr
:所有元素的或值。fixed
:遍历每个元素时,将当前allOr
与当前元素的按位与结果累加到fixed
中。这样,fixed
保存了至少在两个元素中出现的位。
遍历每个元素:
对于每个元素x
,计算(allOr ^ x) | fixed | (x << k)
,其中:allOr ^ x
:去掉x
后的或值(可能错误地清除某些位)。fixed
:修正被错误清除的位(这些位至少在两个元素中出现)。x << k
:将x
左移k
位后的结果。
示例代码
class Solution:
def maximumOr(self, nums: List[int], k: int) -> int:
all_or = fixed = 0
for x in nums:
fixed |= all_or & x # 记录至少出现两次的位
all_or |= x # 所有元素的或值
# 计算每个元素左移k后的最大或值
return max((all_or ^ x) | fixed | (x << k) for x in nums)
复杂度分析
时间复杂度:两种方法均为 O (n),其中 n 为数组长度。
空间复杂度:
方法一:O (n) (需要存储后缀或数组)。
方法二:O (n) (仅使用常数空间)。
关键概念解释
或操作的性质:
或操作的结果在二进制中,只要某一位有一个 1,结果就是 1。因此,左移k
次可以显著增加数值的二进制长度,从而提升或值。固定位
fixed
的作用:
例如,若多个元素在某一位上为 1,则去掉任意一个元素后,该位仍为 1。fixed
记录这些位,确保在计算时不会错误地清除它们。异或操作的作用:
allOr ^ x
的作用是 “假设” 去掉x
后的或值,但可能错误地清除某些位。fixed
修正这些错误,最终得到正确的或值。
算法总结
方法一通过预处理前后缀或数组,快速计算每个元素左移后的或值。
方法二通过数学优化,避免存储前后缀数组,空间效率更高。
两种方法均通过贪心策略,选择最优的元素进行左移操作,确保最终结果最大。