最大或值

标签: 贪心 位运算 数组 前缀和

难度: Medium

给你一个下标从 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

Submission

运行时间: 72 ms

内存: 28.5 MB

class Solution:
    def maximumOr(self, nums: List[int], k: int) -> int:
        # v2是某一位有多个数都是1的情况
        v = v2 = 0
        for x in nums:
            v2 |= v & x
            v |= x

        # 所有位都有多个数为1,并且每一位都是1,那么位移最大数即可
        
        if v2 == v and (v + 1 & v) == 0:
            return max(nums) << k | v

        return max(x << k | x ^ v | v2 for x in nums)

Explain

该题解主要通过位运算的方法来尝试在进行最多k次数值翻倍的操作后,获取最大的按位或结果。首先,使用两个变量v和v2,其中v存储数组nums中所有元素的按位或结果,v2存储在某些位上至少有两个数都为1的情况的按位或结果。接着,如果v2与v相同,并且v加1后与自身进行按位与的结果为0(说明v是2的幂减一的形式),则直接返回最大数左移k位后与v进行按位或的结果。否则,对于每个数x,计算x左移k位后与其自身与v按位异或后的结果再与v2按位或的结果,并返回这些计算中的最大值。

时间复杂度: O(n)

空间复杂度: O(1)

class Solution:
    def maximumOr(self, nums: List[int], k: int) -> int:
        # v记录所有元素的按位或结果
        # v2记录至少两个元素在某些位上都为1的按位或结果
        v = v2 = 0
        for x in nums:
            v2 |= v & x  # 更新v2: 在v已有1的位上,如果x也有1,则v2在该位上也置1
            v |= x  # 更新v: 计算所有元素的按位或

        # 检查v是否为全1且v2等于v,且v+1后与v的按位与结果为0(检查v是否是2的幂减一)
        if v2 == v and (v + 1 & v) == 0:
            return max(nums) << k | v  # 如果是,直接返回最大数左移k位与v按位或的结果

        # 否则,对于每个元素x,计算x左移k位后与其与v的按位异或结果与v2按位或的最大值
        return max(x << k | x ^ v | v2 for x in nums)

Explore

`v`变量用于记录数组`nums`中所有元素的按位或结果。这意味着在数组的所有数字中,只要某一位上至少有一个数是1,那么`v`在该位上也是1。`v2`变量用于记录在`v`已记录位为1的前提下,至少有两个元素在同一位上都为1的情况。这有助于在特定情况下直接计算可能的最大按位或值,特别是当`v`表示一个完整的位模式时(即所有可能的位都被设置为1)。

条件`v2 == v`检查是否每个由`v`标记为1的位至少有两个数字在该位上也是1,这意味着通过任何组合的按位或操作,这些1的位不会改变。条件`(v + 1 & v) == 0`是用来检查`v`是否是形如`111...111`(二进制下全1的形式,例如`3`即`11`或`7`即`111`),这表明`v`已经是最大可能的按位或结果。如果这两个条件都满足,意味着无论如何左移或修改最大数,最终的按位或结果都不会超过`v`,因此可以直接使用最大数左移k位后与v按位或得到最终结果。

这行代码`v2 |= v & x`的作用是更新`v2`的值,以包含那些在`v`已经有1的位上,当前元素`x`也为1的位。首先,`v & x`操作产生一个新的数,该数仅在`v`和`x`都为1的位上为1。然后,通过`|=`操作(按位或赋值),将这些位添加到`v2`中。这样,`v2`最终记录了所有至少有两个数字共同为1的位,帮助在特殊情况下直接得到最大按位或值。

将最大数左移k位的优势在于能够增大其数值,从而可能增加最终的按位或结果。左移操作本质上是将数字乘以`2^k`,这会在数的二进制表示的末尾添加k个0。当这个较大的数与其他数进行按位或操作时,由于它提供了更高位的有效位,因此可以最大化按位或的结果。特别是当其他操作无法提供更高位的有效位时,这种方法尤其有用。