2166-设计位集
位集 Bitset 是一种能以紧凑形式存储位的数据结构。
请你实现 Bitset
类。
Bitset(int size)
用size
个位初始化 Bitset ,所有位都是0
。void fix(int idx)
将下标为idx
的位上的值更新为1
。如果值已经是1
,则不会发生任何改变。void unfix(int idx)
将下标为idx
的位上的值更新为0
。如果值已经是0
,则不会发生任何改变。void flip()
翻转 Bitset 中每一位上的值。换句话说,所有值为0
的位将会变成1
,反之亦然。boolean all()
检查 Bitset 中 每一位 的值是否都是1
。如果满足此条件,返回true
;否则,返回false
。boolean one()
检查 Bitset 中 是否 至少一位 的值是1
。如果满足此条件,返回true
;否则,返回false
。int count()
返回 Bitset 中值为 1 的位的 总数 。String toString()
返回 Bitset 的当前组成情况。注意,在结果字符串中,第i
个下标处的字符应该与 Bitset 中的第i
位一致。
示例:
**输入**
["Bitset", "fix", "fix", "flip", "all", "unfix", "flip", "one", "unfix", "count", "toString"]
[[5], [3], [1], [], [], [0], [], [], [0], [], []]
**输出**
[null, null, null, null, false, null, null, true, null, 2, "01010"]
**解释**
Bitset bs = new Bitset(5); // bitset = "00000".
bs.fix(3); // 将 idx = 3 处的值更新为 1 ,此时 bitset = "00010" 。
bs.fix(1); // 将 idx = 1 处的值更新为 1 ,此时 bitset = "01010" 。
bs.flip(); // 翻转每一位上的值,此时 bitset = "10101" 。
bs.all(); // 返回 False ,bitset 中的值不全为 1 。
bs.unfix(0); // 将 idx = 0 处的值更新为 0 ,此时 bitset = "00101" 。
bs.flip(); // 翻转每一位上的值,此时 bitset = "11010" 。
bs.one(); // 返回 True ,至少存在一位的值为 1 。
bs.unfix(0); // 将 idx = 0 处的值更新为 0 ,此时 bitset = "01010" 。
bs.count(); // 返回 2 ,当前有 2 位的值为 1 。
bs.toString(); // 返回 "01010" ,即 bitset 的当前组成情况。
提示:
1 <= size <= 105
0 <= idx <= size - 1
- 至多调用
fix
、unfix
、flip
、all
、one
、count
和toString
方法 总共105
次 - 至少调用
all
、one
、count
或toString
方法一次 - 至多调用
toString
方法5
次
方法一:利用数组实现
思路与算法
根据题意,我们需要在 O(1) 的时间复杂度内实现 fix()}, \texttt{unfix()}, \texttt{flip()}, \texttt{all()}, \texttt{one()}, \texttt{count() 方法,并在 O(n) 的时间复杂度内实现 toString() 方法。
我们首先考虑用一个长度为 size 的数组 arr 来实现 bitset,其中第 k 个元素的数值等于第 k 位的数值。它支持在 O(1) 的时间复杂度内实现 fix()}, \texttt{unfix() 这两个方法,但其他方法都需要 O(n) 来实现。
进一步地,考虑到每一次执行 fix()}, \texttt{unfix() 操作时,都最多只有一个位被翻转,因此 1 的位数最多改变 1。同时,在 flip() 操作前后 1 的位数之和即为 size。
那么我们可以用额外的整数 cnt 来维护 1 的位数。cnt 的初始值为 0,在每次更新 arr 时,我们都对应更新 cnt 的数值。基于此,我们可以在不影响其余方法的复杂度时,在 O(1) 的时间复杂度内实现 all()}, \texttt{one()}, \texttt{count() 这三个方法。
至此,我们只剩 flip() 方法的复杂度不符合要求。由于反转两次等于不变,且反转和赋值操作可交换,因此我们也可以通过一个二进制位 reversed 来表示反转操作的次数奇偶性。reversed 的初值为 0;在每次 flip() 时,我们可以通过对 reversed 与 1 取异或来代替翻转整个数组。除此以外,第 k 位的数值即为 arr}[k] \oplus \textit{reversed,其中 \oplus 为按位异或。同理,在执行某一位赋值操作时,如果该位的数值会发生变化,我们只需要将该数值与 1 取异或即可。
按照上面的方法,我们就可以在不影响其余操作的时间复杂度下,将 flip() 方法的复杂度也降低为 O(1)。
综上,我们可以通过以下三个元素来实现 Bitset 类:
arr:长度为 size 的数组,用来存储每一位的取值;
cnt:整数,用来统计 1 的位数;
reversed:二进制位,用来统计反转操作的次数奇偶性。
各个方法的具体实现详见代码。
代码
1 | class Bitset { |
1 | class Bitset: |
复杂度分析
时间复杂度:O(k_1n + k_2),其中 n 为位集的长度size,k_1 为 toString() 操作的次数,k_2 为其他操作的次数。预处理和单次 toString() 操作的时间复杂度均为 O(n),其余单次操作的时间复杂度为 O(1)。
空间复杂度:O(n),即为辅助数组的空间开销。