coverPiccoverPic

Leetcode 26 设计位集

前言

今天遇到一个位集(BitSet)的算法题Leetcode的设计位集。位集其实是一种用小空间存储一组布尔值的数据结构,经过位集的优化,每个布尔值可以仅占1位的空间。这个算法题就是设计一个位集。

题解

根据平时的做题经验,利用Unit16Array来存储布尔值。每个index可以存储16位布尔值,设置值的时候通过位移运算把要操作的下标映射到Unit16Array中。

ts
  1. class Bitset {
  2. list?: Uint16Array
  3. size: number
  4. ceil: number
  5. lastMax: number
  6. lastBit: number
  7. unit: number = 16
  8. logUnit: number = ~~Math.log2(this.unit)
  9. max = 2 ** this.unit - 1
  10. trueCount: number = 0
  11. constructor(size: number) {
  12. this.list = new Uint16Array(Math.ceil(size / this.unit))
  13. this.size = size
  14. this.lastMax = (1 << (this.size % this.unit || this.unit)) - 1
  15. this.ceil = Math.ceil(this.size / this.unit) * this.unit
  16. this.lastBit = size % this.unit || this.unit
  17. }
  18. isLast (idx: number) {
  19. return this.ceil - this.unit <= idx
  20. }
  21. getCurrentBit (idx: number) {
  22. return this.isLast(idx) ? this.lastBit : this.unit
  23. }
  24. getCurrentMax (idx: number) {
  25. return this.isLast(idx) ? this.lastMax : this.max
  26. }
  27. fix (idx: number): void {
  28. const { list, unit, logUnit } = this
  29. if (list) {
  30. let pre = list[idx >>> logUnit]
  31. list[idx >>> logUnit] |= 1 << (this.getCurrentBit(idx) - 1 - idx % unit)
  32. pre !== list[idx >>> logUnit] && this.trueCount++
  33. }
  34. }
  35. unfix (idx: number): void {
  36. const { list, max, unit, logUnit } = this
  37. if (list) {
  38. let pre = list[idx >>> logUnit]
  39. list[idx >>> logUnit] &= (max - (1 << (this.getCurrentBit(idx) - 1 - idx % unit)))
  40. pre !== list[idx >>> logUnit] && this.trueCount--
  41. }
  42. }
  43. flip (): void {
  44. const { list, unit, size } = this,
  45. len = list.length
  46. for (let i = 0; i < len; i++) {
  47. list[i] = this.getCurrentMax(i * unit) - list[i]
  48. }
  49. this.trueCount = size - this.trueCount
  50. }
  51. all (): boolean {
  52. const { size, trueCount } = this
  53. return size === trueCount
  54. }
  55. one (): boolean {
  56. const { trueCount } = this
  57. return 0 < trueCount
  58. }
  59. count (): number {
  60. const { trueCount } = this
  61. return trueCount
  62. }
  63. toString (): string {
  64. const { list, lastBit, unit } = this,
  65. len = list.length
  66. let ans = ''
  67. for (let i = 0; i < len; i++) {
  68. const el = list[i]
  69. let factor = (this.isLast(i * unit) ? 1 << (lastBit - 1) : 1 << (unit - 1))
  70. while (factor > 0) {
  71. ans += (el & factor) !== 0
  72. ? 1
  73. : 0
  74. factor >>>= 1
  75. }
  76. }
  77. return ans
  78. }
  79. }

耗时1400+ms,参考了一下官方题解,发现这个题目用例中,对位集的反转操作非常频繁,所以需要换个思路,懒标记反转操作,只有在toString操作时实际进行反转操作。
我们都知道,反转操作和设置操作是可以交换顺序的:0置1,然后反转,和先反转,然后把1置0,结果一样的,于是:

ts
  1. class Bitset {
  2. list: Uint16Array
  3. size: number
  4. ceil: number
  5. lastMax: number
  6. lastBit: number
  7. unit: number = 16
  8. logUnit: number = ~~Math.log2(this.unit)
  9. max = 2 ** this.unit - 1
  10. trueCount: number = 0
  11. filpTimes: boolean = false
  12. constructor(size: number) {
  13. this.list = new Uint16Array(Math.ceil(size / this.unit))
  14. this.size = size
  15. this.lastMax = (1 << (this.size % this.unit || this.unit)) - 1
  16. this.ceil = Math.ceil(this.size / this.unit) * this.unit
  17. this.lastBit = size % this.unit || this.unit
  18. }
  19. isLast (idx: number) {
  20. return this.ceil - this.unit <= idx
  21. }
  22. getCurrentBit (idx: number) {
  23. return this.isLast(idx) ? this.lastBit : this.unit
  24. }
  25. getCurrentMax (idx: number) {
  26. return this.isLast(idx) ? this.lastMax : this.max
  27. }
  28. fix(idx: number, odd: boolean): void {
  29. if ((this.filpTimes) && !odd) {
  30. this.unfix(idx, true)
  31. return
  32. }
  33. const { list, unit, logUnit } = this
  34. if (list) {
  35. let pre = list[idx >>> logUnit]
  36. list[idx >>> logUnit] |= 1 << (this.getCurrentBit(idx) - 1 - idx % unit)
  37. pre !== list[idx >>> logUnit] && (!odd
  38. ? this.trueCount++
  39. : this.trueCount--)
  40. }
  41. }
  42. unfix(idx: number, odd: boolean): void {
  43. if ((this.filpTimes) && !odd) {
  44. this.fix(idx, true)
  45. return
  46. }
  47. const { list, max, unit, logUnit } = this
  48. if (list) {
  49. let pre = list[idx >>> logUnit]
  50. list[idx >>> logUnit] &= (max - (1 << (this.getCurrentBit(idx) - 1 - idx % unit)))
  51. pre !== list[idx >>> logUnit] && (!odd
  52. ? this.trueCount--
  53. : this.trueCount++)
  54. }
  55. }
  56. flip(): void {
  57. this.filpTimes = !this.filpTimes
  58. this.trueCount = this.size - this.trueCount
  59. }
  60. all(): boolean {
  61. const { size, trueCount } = this
  62. return size === trueCount
  63. }
  64. one(): boolean {
  65. const { trueCount } = this
  66. return 0 < trueCount
  67. }
  68. count(): number {
  69. const { trueCount } = this
  70. return trueCount
  71. }
  72. toString(): string {
  73. const { list, unit, filpTimes } = this,
  74. len = list.length
  75. let ans = ''
  76. for (let i = 0; i < len; i++) {
  77. const el = filpTimes ? this.getCurrentMax(i * unit) - list[i] : list[i]
  78. let len = this.getCurrentBit(i * unit)
  79. const temp = el.toString(2)
  80. ans += '0'.repeat(len - temp.length) + temp
  81. }
  82. return ans
  83. }
  84. }

这个方法耗时~700ms,效率大幅提升。另外,看题解看到使用Set存储被fixunfix操作的值,感觉是由于fixunfix操作用例中不是很频繁,结合反转的标记,toString方法调用的时候就可以用少量空间还原出整个位集。

ts
  1. class Bitset {
  2. size: number;
  3. set: Set<number>;
  4. isOne: boolean; // 等于true表示set中存的是值为1的下标
  5. constructor(size: number) {
  6. this.size = size;
  7. this.set = new Set();
  8. this.isOne = true;
  9. }
  10. fix(idx: number): void {
  11. if (this.isOne) {
  12. this.set.add(idx);
  13. } else {
  14. this.set.delete(idx);
  15. }
  16. }
  17. unfix(idx: number): void {
  18. if (this.isOne) {
  19. this.set.delete(idx);
  20. } else {
  21. this.set.add(idx);
  22. }
  23. }
  24. flip(): void {
  25. this.isOne = !this.isOne;
  26. }
  27. all(): boolean {
  28. return this.isOne ? this.set.size === this.size : this.set.size === 0;
  29. }
  30. one(): boolean {
  31. return this.isOne ? this.set.size > 0 : this.set.size < this.size;
  32. }
  33. count(): number {
  34. if (this.isOne) {
  35. return this.set.size;
  36. } else {
  37. return this.size - this.set.size;
  38. }
  39. }
  40. toString(): string {
  41. if (this.isOne) {
  42. const arr = new Array(this.size).fill('0');
  43. for (const idx of this.set) {
  44. arr[idx] = '1';
  45. }
  46. return arr.join('');
  47. } else {
  48. const arr = new Array(this.size).fill('1');
  49. for (const idx of this.set) {
  50. arr[idx] = '0';
  51. }
  52. return arr.join('');
  53. }
  54. }
  55. }
3 条评论未登录用户
Ctrl or + Enter 评论
尊嘟可爱蓝莓
喵喵
2 years agoReply to
lixinghai
很厉害的样子
2 years agoReply to
KokoroAme
贴贴
2 years agoReply to
© 2023-2025 LittleRangiferTarandus, All rights reserved.
🌸 Run