2044. 统计按位或能得到最大值的子集数目
2044. 统计按位或能得到最大值的子集数目
🟠 🔖 位运算 数组 回溯 枚举 🔗 力扣 LeetCode
题目
Given an integer array nums, find the maximum possible bitwise OR of a subset of nums and return the number of different non-empty subsets with the maximum bitwise OR.
An array a is a subset of an array b if a can be obtained from b by deleting some (possibly zero) elements of b. Two subsets are considered different if the indices of the elements chosen are different.
The bitwise OR of an array a is equal to a[0] **OR** a[1] **OR** ... **OR** a[a.length - 1] (0-indexed).
Example 1:
Input: nums = [3,1]
Output: 2
Explanation: The maximum possible bitwise OR of a subset is 3. There are 2 subsets with a bitwise OR of 3:
- [3]
- [3,1]
Example 2:
Input: nums = [2,2,2]
Output: 7
Explanation: All non-empty subsets of [2,2,2] have a bitwise OR of 2. There are 2^3 - 1 = 7 total subsets.
Example 3:
Input: nums = [3,2,1,5]
Output: 6
Explanation: The maximum possible bitwise OR of a subset is 7. There are 6 subsets with a bitwise OR of 7:
- [3,5]
- [3,1,5]
- [3,2,5]
- [3,2,1,5]
- [2,5]
- [2,1,5]
Constraints:
1 <= nums.length <= 161 <= nums[i] <= 10^5
题目大意
给你一个整数数组 nums ,请你找出 nums 子集 按位或 可能得到的最大值 ,并返回按位或能得到最大值的 不同非空子集的数目 。
如果数组 a 可以由数组 b 删除一些元素(或不删除)得到,则认为数组 a 是数组 b 的一个 子集 。如果选中的元素下标位置不一样,则认为两个子集 不同 。
对数组 a 执行 按位或 ,结果等于 a[0] OR a[1] OR ... OR a[a.length - 1](下标从 0 开始)。
示例 1:
输入: nums = [3,1]
输出: 2
解释: 子集按位或能得到的最大值是 3 。有 2 个子集按位或可以得到 3 :
- [3]
- [3,1]
示例 2:
输入: nums = [2,2,2]
输出: 7
解释:[2,2,2] 的所有非空子集的按位或都可以得到 2 。总共有 2^3 - 1 = 7 个子集。
示例 3:
输入: nums = [3,2,1,5]
输出: 6
解释: 子集按位或可能的最大值是 7 。有 6 个子集按位或可以得到 7 :
- [3,5]
- [3,1,5]
- [3,2,5]
- [3,2,1,5]
- [2,5]
- [2,1,5]
提示:
1 <= nums.length <= 161 <= nums[i] <= 10^5
解题思路
思路一:回溯
这道题目还可以使用回溯来做,对应到算法详解《3.4 回溯算法》 中讲到的「元素可重不可复选 - 子集」题型。
初始化:
- 创建一个变量
maxOR用于存储当前找到的最大按位或值,另一个变量count用于记录可以得到该最大值的不同非空子集的数量。 - 使用一个
track变量来维护当前子集的按位或值。
- 创建一个变量
回溯函数:
- 定义一个回溯函数
backtrack(start),用于生成从start开始的所有子集。 - 在每次递归中,遍历当前索引
start到数组末尾的所有元素,进行如下操作:- 记录状态:在处理当前元素前,保存当前的
track值。 - 更新
track:将当前元素与track进行按位或运算,更新track。 - 更新最大值和计数:
- 如果当前
track的值大于maxOR,则更新maxOR并将count重置为 1(表示找到一个新的最大值)。 - 如果当前
track等于maxOR,则将count加 1(表示找到一个符合条件的子集)。
- 如果当前
- 递归调用:递归地调用
backtrack(i + 1),继续处理下一个元素。 - 恢复状态:在返回之前,将
track恢复到之前的状态,以便进行下一次迭代。
- 记录状态:在处理当前元素前,保存当前的
- 定义一个回溯函数
启动回溯:
- 从索引 0 开始调用
backtrack(0),以生成所有可能的子集。
- 从索引 0 开始调用
返回结果:
- 函数结束后返回
count,即能够生成最大按位或值的不同非空子集的数量。
- 函数结束后返回
复杂度分析
- 时间复杂度:
O(2^n),其中 n 是数组的长度,每个元素可以选择加入或不加入子集中,最多会有2^n个子集,最坏情况下需要检查所有子集。 - 空间复杂度:
O(n),主要开销是递归栈的空间,递归调用的深度最多为n。
思路二:隐式回溯
可以对上面的代码进一步优化。
隐式回溯 给回溯函数增加一个参数
currentOR,直接通过currentOR参数来跟踪当前的按位或值,将track的更新逻辑移除,隐式地回溯,简化状态管理。直接求得最大按位或值
maxOR按位或运算(|)对两个数的每一位进行比较,只要有一位是 1,结果的该位就是 1。因此,按位或的结果会保留所有参与运算数的 1。所以最大按位或值
maxOR就是将数组中的所有元素进行按位或运算,因为它将包含所有元素在其二进制表示中存在的所有 1 位。即:
maxOR = nums.reduce((acc, num) => acc | num, 0)因此可以直接求得
maxOR,移除在递归中计算最大值的逻辑,简化状态管理。
复杂度分析同上。
代码
/**
* @param {number[]} nums
* @return {number}
*/
var countMaxOrSubsets = function (nums) {
const n = nums.length;
let track = 0,
maxOR = 0,
count = 0;
const backtrack = (start) => {
for (let i = start; i < n; i++) {
// 记录当前状态
const currentTrack = track;
// 更新 track
track |= nums[i];
// 根据 track 的值更新 maxOR 和 count
if (track > maxOR) {
maxOR = track;
count = 1; // 找到新的最大值,重置计数
} else if (track === maxOR) {
count++; // 当前值等于最大值,计数加 1
}
// 递归调用
backtrack(i + 1);
// 恢复 track 的状态
track = currentTrack;
}
};
backtrack(0);
return count;
};
/**
* @param {number[]} nums
* @return {number}
*/
var countMaxOrSubsets = function (nums) {
const n = nums.length;
const maxOR = nums.reduce((acc, num) => acc | num, 0);
let count = 0;
const backtrack = (start, currentOR) => {
for (let i = start; i < n; i++) {
const newOR = currentOR | nums[i];
if (newOR === maxOR) {
count++; // 如果当前按位或值等于最大值,计数加 1
}
backtrack(i + 1, newOR); // 递归调用,更新 currentOR
}
};
backtrack(0, 0); // 从索引 0 开始,初始按位或值为 0
return count;
};
相关题目
| 题号 | 标题 | 题解 | 标签 | 难度 | 力扣 |
|---|---|---|---|---|---|
| 78 | 子集 | [✓] | 位运算 数组 回溯 | 🟠 | 🀄️ 🔗 |
| 2275 | 按位与结果大于零的最长组合 | [✓] | 位运算 数组 哈希表 1+ | 🟠 | 🀄️ 🔗 |
| 2419 | 按位与最大的最长子数组 | 位运算 脑筋急转弯 数组 | 🟠 | 🀄️ 🔗 |
