diff --git "a/LeetCode/2041-2050/2044. \347\273\237\350\256\241\346\214\211\344\275\215\346\210\226\350\203\275\345\276\227\345\210\260\346\234\200\345\244\247\345\200\274\347\232\204\345\255\220\351\233\206\346\225\260\347\233\256\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/2041-2050/2044. \347\273\237\350\256\241\346\214\211\344\275\215\346\210\226\350\203\275\345\276\227\345\210\260\346\234\200\345\244\247\345\200\274\347\232\204\345\255\220\351\233\206\346\225\260\347\233\256\357\274\210\344\270\255\347\255\211\357\274\211.md" new file mode 100644 index 00000000..7e8d37fd --- /dev/null +++ "b/LeetCode/2041-2050/2044. \347\273\237\350\256\241\346\214\211\344\275\215\346\210\226\350\203\275\345\276\227\345\210\260\346\234\200\345\244\247\345\200\274\347\232\204\345\255\220\351\233\206\346\225\260\347\233\256\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -0,0 +1,130 @@ +### 题目描述 + +这是 LeetCode 上的 **[2044. 统计按位或能得到最大值的子集数目]()** ,难度为 **中等**。 + +Tag : 「二进制枚举」、「位运算」、「回溯算法」 + + + +给你一个整数数组 $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] +``` + +提示: +* $1 <= nums.length <= 16$ +* $1 <= nums[i] <= 10^5$ + +--- + +### 二进制枚举 + +令 $n$ 为 $nums$ 的长度,利用 $n$ 不超过 $16$,我们可以使用一个 `int` 数值来代指 $nums$ 的使用情况(子集状态)。 + +假设当前子集状态为 $state$,$state$ 为一个仅考虑低 $n$ 位的二进制数,当第 $k$ 位为 $1$,代表 $nums[k]$ 参与到当前的按位或运算,当第 $k$ 位为 $0$,代表 $nums[i]$ 不参与到当前的按位或运算。 + +在枚举这 $2^n$ 个状态过程中,我们使用变量 `max` 记录最大的按位或得分,使用 `ans` 记录能够取得最大得分的状态数量。 + +代码: +```Java +class Solution { + public int countMaxOrSubsets(int[] nums) { + int n = nums.length, mask = 1 << n; + int max = 0, ans = 0; + for (int s = 0; s < mask; s++) { + int cur = 0; + for (int i = 0; i < n; i++) { + if (((s >> i) & 1) == 1) cur |= nums[i]; + } + if (cur > max) { + max = cur; ans = 1; + } else if (cur == max) { + ans++; + } + } + return ans; + } +} +``` +* 时间复杂度:令 $nums$ 长度为 $n$,共有 $2^n$ 个子集状态,计算每个状态的按位或答案复杂度为 $O(n)。$整体复杂度为 $O(2^n * n)$ +* 空间复杂度:$O(1)$ + +--- + +### 回溯算法 + + + +代码: +```Java +class Solution { + int[] nums; + int max = 0, ans = 0; + public int countMaxOrSubsets(int[] _nums) { + nums = _nums; + dfs(0, 0); + return ans; + } + void dfs(int u, int val) { + if (u == nums.length) { + if (val > max) { + max = val; ans = 1; + } else if (val == max) { + ans++; + } + return ; + } + dfs(u + 1, val); + dfs(u + 1, val | nums[u]); + } +} +``` +* 时间复杂度:令 $nums$ 长度为 $n$,共有 $2^n$ 个子集状态。$整体复杂度为 $O(2^n * n)$ +* 空间复杂度:忽略递归带来的额外空间开销,复杂度为 $O(1)$ + +--- + +### 最后 + +这是我们「刷穿 LeetCode」系列文章的第 `No.2044` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。 + +在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。 + +为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。 + +在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。 + diff --git "a/LeetCode/591-600/599. \344\270\244\344\270\252\345\210\227\350\241\250\347\232\204\346\234\200\345\260\217\347\264\242\345\274\225\346\200\273\345\222\214\357\274\210\347\256\200\345\215\225\357\274\211.md" "b/LeetCode/591-600/599. \344\270\244\344\270\252\345\210\227\350\241\250\347\232\204\346\234\200\345\260\217\347\264\242\345\274\225\346\200\273\345\222\214\357\274\210\347\256\200\345\215\225\357\274\211.md" index a21ec2d4..c4aa04a4 100644 --- "a/LeetCode/591-600/599. \344\270\244\344\270\252\345\210\227\350\241\250\347\232\204\346\234\200\345\260\217\347\264\242\345\274\225\346\200\273\345\222\214\357\274\210\347\256\200\345\215\225\357\274\211.md" +++ "b/LeetCode/591-600/599. \344\270\244\344\270\252\345\210\227\350\241\250\347\232\204\346\234\200\345\260\217\347\264\242\345\274\225\346\200\273\345\222\214\357\274\210\347\256\200\345\215\225\357\274\211.md" @@ -79,6 +79,32 @@ class Solution { --- +### 答疑 + +评论区有位同学提出了一个挺有意思的疑问,或许是部分同学的共同疑问,这里集中答疑一下。 + +Q0: `for` 循环里的 `ans.clear()` 这个函数也是 $O(n)$ 复杂度吧,为什么合起来还是 $O(n)$ ? + +A0: 在 `ArrayList` 源码中的 `clear` 实现会为了消除容器对对象的强引用,遍历容器内的内容并置空来帮助 GC。 + +但不代表这会导致复杂度上界变成 $n^2$。 + +不会导致复杂度退化的核心原因是:**由于 `clear` 导致的循环计算量总共必然不会超过 $n$**。因为最多只有 $n$ 个元素在 `ans` 里面,且同一元素不会被删除多次(即每个元素对 `clear` 的贡献不会超过 $1$)。 + +如果有同学还是觉得不好理解,可以考虑一种极端情况:`clear` 操作共发生 $n$ 次,但发生 $n$ 次的前提条件是每次 `ans` 中只有 $1$ 位元素,此时由 `clear` 操作带来的额外计算量为最大值 $n$。 + +因此这里的 `clear` 操作对复杂度影响是「累加」,而不是「累乘」,即复杂度仍为 $O(n)$,而不是 $O(n^2)$。 + +
+ +Q1: 判断 $list[i]$ 是否在哈希中的操作,复杂度是多少? + +A1: 在 Java 的 `HashMap` 实现中,当键值对中的键数据类型为 `String` 时,会先计算一次(之后使用缓存)该字符串的 `HashCode`,计算 `HashCode` 的过程需要遍历字符串,因此该操作是与字符串长度相关的(对于本题字符串长度不超过 $30$),然后根据 `HashCode`「近似」$O(1)$ 定位到哈希桶位置并进行插入/更新。 + +因此在 Java 中,该操作与「当前的字符串长度」相关,而与「当前容器所包含元素多少」无关。 + +--- + ### 最后 这是我们「刷穿 LeetCode」系列文章的第 `No.599` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。