leetcode-统计按位或能得到最大值的子集数目

121 阅读3分钟

Offer 驾到,掘友接招!我正在参与2022春招系列活动-刷题打卡任务,点击查看活动详情

题目描述

给你一个整数数组 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 。总共有 23 - 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]

思路分析

暴力

暴力的方法比较容易想到,假设nums数组包含len个元素,那么对于组成nums的子集,每个元素都有取或者不取这2种情况,所以总共有2^len-1种子集的可能(要去掉一个不包含任何元素的子集)。对于这些子集,每次都去计算按位或的结果,然后再找到最大的结果并计算个数。由于子集包含的元素各位平均为len/2,所以这种方法的时间复杂度为O((2^len-1)len/2),即O(len2^len)

带备忘录的回溯

2044.png 暴力的方法缺点很明显,就是对于每个自己,都要重新计算按位或的结果,其实中间有很多计算都是重复的,所以我们可以这么考虑:把按位或的过程看成是一棵树,根节点结果为0(因为0|n=n,不影响结果),第0层有2种情况,即num(0)包含或者不包含,在这一层会生成2个节点,节点值分别是 父节点|num(0)(包含num(0)的情况) 和 父节点(不包含num(0)的情况);然后第1层,对num(1)也有包含或者不包含的2种情况,所以上1层的2个节点各有2个子节点,分别对应num(1)包含和不包含的情况,计算这一层节点值可以用第0层的节点值。这样循环下去,计算每1层节点值,不用每次都是根节点开始计算,而是用上一层节点的值和当前num值,常数时间就可以计算出来。

Java版本代码
class Solution {

    private static int max2044 = 0;

    private static int ans2044 = 0;

    public int countMaxOrSubsets(int[] nums) {
        max2044 = 0;
        ans2044 = 0;
        dfs2044(nums, 0, 0);
        return ans2044;
    }

    private static void dfs2044(int[] nums, int nextPosition, int currentVal) {
        if (nextPosition == nums.length) {
            if (currentVal > max2044) {
                max2044 = currentVal;
                ans2044 = 1;
            } else if (currentVal == max2044) {
                ans2044++;
            }
            return;
        }
        // 包含当前
        dfs2044(nums, nextPosition + 1, currentVal|nums[nextPosition]);
        // 不包含当前
        dfs2044(nums, nextPosition + 1, currentVal);
    }
}