前言
今天遇到一个位集(BitSet)的算法题Leetcode的设计位集。位集其实是一种用小空间存储一组布尔值的数据结构,经过位集的优化,每个布尔值可以仅占1位的空间。这个算法题就是设计一个位集。
题解
根据平时的做题经验,利用Unit16Array来存储布尔值。每个index可以存储16位布尔值,设置值的时候通过位移运算把要操作的下标映射到Unit16Array中。
ts- class Bitset {
- list?: Uint16Array
- size: number
- ceil: number
- lastMax: number
- lastBit: number
- unit: number = 16
- logUnit: number = ~~Math.log2(this.unit)
- max = 2 ** this.unit - 1
- trueCount: number = 0
- constructor(size: number) {
- this.list = new Uint16Array(Math.ceil(size / this.unit))
- this.size = size
- this.lastMax = (1 << (this.size % this.unit || this.unit)) - 1
- this.ceil = Math.ceil(this.size / this.unit) * this.unit
- this.lastBit = size % this.unit || this.unit
- }
- isLast (idx: number) {
- return this.ceil - this.unit <= idx
- }
- getCurrentBit (idx: number) {
- return this.isLast(idx) ? this.lastBit : this.unit
- }
- getCurrentMax (idx: number) {
- return this.isLast(idx) ? this.lastMax : this.max
- }
- fix (idx: number): void {
- const { list, unit, logUnit } = this
- if (list) {
- let pre = list[idx >>> logUnit]
- list[idx >>> logUnit] |= 1 << (this.getCurrentBit(idx) - 1 - idx % unit)
- pre !== list[idx >>> logUnit] && this.trueCount++
- }
- }
- unfix (idx: number): void {
- const { list, max, unit, logUnit } = this
- if (list) {
- let pre = list[idx >>> logUnit]
- list[idx >>> logUnit] &= (max - (1 << (this.getCurrentBit(idx) - 1 - idx % unit)))
- pre !== list[idx >>> logUnit] && this.trueCount--
- }
- }
- flip (): void {
- const { list, unit, size } = this,
- len = list.length
- for (let i = 0; i < len; i++) {
- list[i] = this.getCurrentMax(i * unit) - list[i]
- }
- this.trueCount = size - this.trueCount
- }
- all (): boolean {
- const { size, trueCount } = this
- return size === trueCount
- }
- one (): boolean {
- const { trueCount } = this
- return 0 < trueCount
- }
- count (): number {
- const { trueCount } = this
- return trueCount
- }
- toString (): string {
- const { list, lastBit, unit } = this,
- len = list.length
- let ans = ''
- for (let i = 0; i < len; i++) {
- const el = list[i]
- let factor = (this.isLast(i * unit) ? 1 << (lastBit - 1) : 1 << (unit - 1))
- while (factor > 0) {
- ans += (el & factor) !== 0
- ? 1
- : 0
- factor >>>= 1
- }
- }
- return ans
- }
- }
耗时1400+ms,参考了一下官方题解,发现这个题目用例中,对位集的反转操作非常频繁,所以需要换个思路,懒标记反转操作,只有在toString操作时实际进行反转操作。
我们都知道,反转操作和设置操作是可以交换顺序的:0置1,然后反转,和先反转,然后把1置0,结果一样的,于是:
ts- class Bitset {
- list: Uint16Array
- size: number
- ceil: number
- lastMax: number
- lastBit: number
- unit: number = 16
- logUnit: number = ~~Math.log2(this.unit)
- max = 2 ** this.unit - 1
- trueCount: number = 0
- filpTimes: boolean = false
-
- constructor(size: number) {
- this.list = new Uint16Array(Math.ceil(size / this.unit))
- this.size = size
- this.lastMax = (1 << (this.size % this.unit || this.unit)) - 1
- this.ceil = Math.ceil(this.size / this.unit) * this.unit
- this.lastBit = size % this.unit || this.unit
- }
- isLast (idx: number) {
- return this.ceil - this.unit <= idx
- }
- getCurrentBit (idx: number) {
- return this.isLast(idx) ? this.lastBit : this.unit
- }
- getCurrentMax (idx: number) {
- return this.isLast(idx) ? this.lastMax : this.max
- }
- fix(idx: number, odd: boolean): void {
- if ((this.filpTimes) && !odd) {
- this.unfix(idx, true)
- return
- }
- const { list, unit, logUnit } = this
- if (list) {
- let pre = list[idx >>> logUnit]
- list[idx >>> logUnit] |= 1 << (this.getCurrentBit(idx) - 1 - idx % unit)
- pre !== list[idx >>> logUnit] && (!odd
- ? this.trueCount++
- : this.trueCount--)
- }
- }
- unfix(idx: number, odd: boolean): void {
- if ((this.filpTimes) && !odd) {
- this.fix(idx, true)
- return
- }
- const { list, max, unit, logUnit } = this
- if (list) {
- let pre = list[idx >>> logUnit]
- list[idx >>> logUnit] &= (max - (1 << (this.getCurrentBit(idx) - 1 - idx % unit)))
- pre !== list[idx >>> logUnit] && (!odd
- ? this.trueCount--
- : this.trueCount++)
- }
- }
- flip(): void {
- this.filpTimes = !this.filpTimes
- this.trueCount = this.size - this.trueCount
- }
- all(): boolean {
- const { size, trueCount } = this
- return size === trueCount
- }
- one(): boolean {
- const { trueCount } = this
- return 0 < trueCount
- }
- count(): number {
- const { trueCount } = this
- return trueCount
- }
- toString(): string {
- const { list, unit, filpTimes } = this,
- len = list.length
- let ans = ''
- for (let i = 0; i < len; i++) {
- const el = filpTimes ? this.getCurrentMax(i * unit) - list[i] : list[i]
- let len = this.getCurrentBit(i * unit)
- const temp = el.toString(2)
- ans += '0'.repeat(len - temp.length) + temp
- }
- return ans
- }
- }
这个方法耗时~700ms,效率大幅提升。另外,看题解看到使用Set存储被fix和unfix操作的值,感觉是由于fix和unfix操作用例中不是很频繁,结合反转的标记,toString方法调用的时候就可以用少量空间还原出整个位集。
ts- class Bitset {
- size: number;
- set: Set<number>;
- isOne: boolean; // 等于true表示set中存的是值为1的下标
- constructor(size: number) {
- this.size = size;
- this.set = new Set();
- this.isOne = true;
- }
- fix(idx: number): void {
- if (this.isOne) {
- this.set.add(idx);
- } else {
- this.set.delete(idx);
- }
- }
- unfix(idx: number): void {
- if (this.isOne) {
- this.set.delete(idx);
- } else {
- this.set.add(idx);
- }
- }
- flip(): void {
- this.isOne = !this.isOne;
- }
- all(): boolean {
- return this.isOne ? this.set.size === this.size : this.set.size === 0;
- }
- one(): boolean {
- return this.isOne ? this.set.size > 0 : this.set.size < this.size;
- }
- count(): number {
- if (this.isOne) {
- return this.set.size;
- } else {
- return this.size - this.set.size;
- }
- }
- toString(): string {
- if (this.isOne) {
- const arr = new Array(this.size).fill('0');
- for (const idx of this.set) {
- arr[idx] = '1';
- }
- return arr.join('');
- } else {
- const arr = new Array(this.size).fill('1');
- for (const idx of this.set) {
- arr[idx] = '0';
- }
- return arr.join('');
- }
- }
- }
3 条评论未登录用户
Ctrl or + Enter 评论
喵喵
2 years agoReply to
很厉害的样子
2 years agoReply to
