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 <= 1050 <= 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),即为辅助数组的空间开销。