leetcode 第 218 场周赛第四题,坑了我许久。看到许多解答是用状态压缩(具体而言,二进制子集枚举)DP 来做的,典型如 大佬z zerotrac 的题解。当中提到:
- 对于位数不超过 n 的二进制数,枚举它的所有子集的时间复杂度为 O(2n)。
- 然而如果我们枚举每一个不超过 n 的二进制数的所有子集,(即,0 枚举子集,1 枚举子集,2 枚举子集,... 2n−1 枚举子集),那么时间复杂度实际上不是 O(2n⋅2n)=O(4n),而是 O(3n),具体可以用二项式定理来证明。
题目中 n 的最大值是 16,所以 4n=232>109 显然会超时,如果复杂度果真如此,这个状压 DP 方法就不能用了。而如果是 3n 则不会超时。所以搞清楚这里的复杂度对于确认思路是非常关键的。
随手证明一下:
考虑 0,1,2,...2n−1 每一个数都做子集枚举。那么,
- 空集(0) 是每个数的子集,因此出现了 2n 次。
- 对于某个大小为 1 的子集,它会被 22n=2n−1 个数包含。原因容易理解,因为 0,1,2,...2n−1 实际上均匀地列出了 2n−1 的各个子集,因此对于某元素来说,会有一半的子集中该存在该元素,而另一半不存在。而大小为 1 的子集共有 Cn1 个。
- 对于某个大小为 2 的子集,它会被 222n=2n−2 个数包含。而大小为 2 的子集共有 Cn2 个。
- ……
- 对于大小为 n 的子集,它会被 2n2n=20 个数包含。而大小为 n 的子集只有一个。(对于全集,只有最后那个全集 2n−1 能枚举到它。)
所以全部加起来,正好是
count=Cn0⋅2n+Cn1⋅2n−1+Cn2⋅2n−2+…+Cnn⋅20。
自动脑补每一项乘以 1 的某些次方,结果正好是 (2+1)n 的二项展开式。因此,每个数枚举子集的复杂度总和确实为 3n。只要写的时候加上合适的预处理和剪枝,过题是没问题的。