2166. Design Bitset
Problem Description
You need to implement a Bitset class that efficiently manages a collection of bits (0s and 1s). The class should support the following operations:
Constructor:
Bitset(size)
: Creates a bitset withsize
bits, all initially set to0
.
Methods:
fix(idx)
: Sets the bit at indexidx
to1
. If it's already1
, nothing changes.unfix(idx)
: Sets the bit at indexidx
to0
. If it's already0
, nothing changes.flip()
: Inverts all bits in the bitset - every0
becomes1
and every1
becomes0
.all()
: Returnstrue
if every bit in the bitset is1
, otherwise returnsfalse
.one()
: Returnstrue
if at least one bit in the bitset is1
, otherwise returnsfalse
.count()
: Returns the total number of1
bits in the bitset.toString()
: Returns a string representation of the bitset where the character at positioni
represents the bit at indexi
.
The solution uses a clever dual-array approach where:
- Array
a
stores the current state of the bitset - Array
b
stores the flipped state of the bitset - Variable
cnt
tracks the count of1
s in arraya
This design makes the flip()
operation extremely efficient (O(1)
) by simply swapping the two arrays and adjusting the count, rather than iterating through all bits. When fixing or unfixing individual bits, both arrays are updated to maintain consistency. The toString()
method returns the joined representation of array a
.
Intuition
The key challenge in this problem is handling the flip()
operation efficiently. A naive approach would iterate through all bits and flip each one, taking O(n)
time. However, when we need to flip frequently, this becomes a bottleneck.
The breakthrough insight is that instead of actually flipping all bits, we can maintain what the flipped version would look like at all times. Think of it like having a photo and its negative - when you need the negative, you don't process the photo each time, you just switch to the pre-computed negative.
This leads us to maintain two arrays:
- Array
a
: represents the current state of our bitset - Array
b
: represents the complement (flipped version) of our bitset
When flip()
is called, we simply swap these arrays - what was the normal becomes the flipped, and what was flipped becomes the normal. This reduces the flip operation from O(n)
to O(1)
.
To make this work, we need to ensure both arrays stay synchronized during fix()
and unfix()
operations. When we set a bit to 1
in array a
, we must set the corresponding bit to 0
in array b
(since it's the complement). Similarly, when we set a bit to 0
in array a
, we set it to 1
in array b
.
We also maintain a counter cnt
to track the number of 1
s in array a
. This allows us to answer count()
, all()
, and one()
queries in O(1)
time without scanning the entire array. When we flip, the new count becomes size - cnt
(all the 0
s become 1
s and vice versa).
This design trades space (using two arrays instead of one) for time efficiency, making all operations except toString()
run in constant time.
Solution Approach
The implementation uses two character arrays and a counter to efficiently manage the bitset operations:
Data Structure:
self.a
: Character array storing the current bitset state (initially all'0'
)self.b
: Character array storing the complement of the bitset (initially all'1'
)self.cnt
: Integer counter tracking the number of'1'
bits in arraya
Constructor (__init__
):
self.a = ['0'] * size # Current state: all zeros self.b = ['1'] * size # Complement: all ones self.cnt = 0 # Count of 1s in array a
Initializes both arrays and sets the counter to 0 since all bits start as '0'
.
fix(idx)
Method:
if self.a[idx] == '0': self.a[idx] = '1' self.cnt += 1 self.b[idx] = '0'
- Only updates array
a
and increments counter if the bit was'0'
- Always sets the corresponding bit in array
b
to'0'
(complement of'1'
)
unfix(idx)
Method:
if self.a[idx] == '1': self.a[idx] = '0' self.cnt -= 1 self.b[idx] = '1'
- Only updates array
a
and decrements counter if the bit was'1'
- Always sets the corresponding bit in array
b
to'1'
(complement of'0'
)
flip()
Method:
self.a, self.b = self.b, self.a
self.cnt = len(self.a) - self.cnt
- Swaps the two arrays in
O(1)
time using Python's tuple unpacking - Updates the counter: if there were
cnt
ones before, there are nowsize - cnt
ones (all zeros became ones)
Query Methods:
all()
: Returnsself.cnt == len(self.a)
- checks if all bits are'1'
one()
: Returnsself.cnt > 0
- checks if at least one bit is'1'
count()
: Returnsself.cnt
- directly returns the maintained countertoString()
: Returns''.join(self.a)
- joins the character array into a string
All operations except toString()
run in O(1)
time. The toString()
method requires O(n)
time to concatenate the array elements. The space complexity is O(n)
for storing the two arrays.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example with a bitset of size 5 to illustrate how the dual-array approach works:
Initial State after Bitset(5)
:
a = ['0', '0', '0', '0', '0'] (current state) b = ['1', '1', '1', '1', '1'] (complement) cnt = 0
Operation 1: fix(1)
- Set index 1 to '1'
Before: a[1] = '0', so we update it After: a = ['0', '1', '0', '0', '0'] b = ['1', '0', '1', '1', '1'] (b[1] set to '0' as complement) cnt = 1
Operation 2: fix(3)
- Set index 3 to '1'
Before: a[3] = '0', so we update it After: a = ['0', '1', '0', '1', '0'] b = ['1', '0', '1', '0', '1'] (b[3] set to '0' as complement) cnt = 2
Operation 3: flip()
- Invert all bits
Instead of iterating through all bits, we simply swap arrays: Before swap: a = ['0', '1', '0', '1', '0'], b = ['1', '0', '1', '0', '1'] After swap: a = ['1', '0', '1', '0', '1'] (old b becomes new a) b = ['0', '1', '0', '1', '0'] (old a becomes new b) cnt = 5 - 2 = 3 (size - old_cnt)
Notice how after the flip:
- The bits that were '0' in the original (indices 0, 2, 4) are now '1'
- The bits that were '1' in the original (indices 1, 3) are now '0'
- This happened in O(1) time by swapping references!
Operation 4: count()
- Get number of 1s
Returns: cnt = 3 (no array traversal needed)
Operation 5: unfix(0)
- Set index 0 to '0'
Before: a[0] = '1', so we update it After: a = ['0', '0', '1', '0', '1'] b = ['1', '1', '0', '1', '0'] (b[0] set to '1' as complement) cnt = 2
Operation 6: toString()
- Get string representation
Returns: "00101" (joining array a)
The key insight is that array b
always maintains the exact opposite of array a
. When we need to flip everything, instead of changing n bits individually, we just swap our perspective - what was the inverse becomes the primary, achieving O(1) flip operation.
Solution Implementation
1class Bitset:
2 def __init__(self, size: int):
3 """
4 Initialize a Bitset of given size with all bits set to 0.
5
6 Args:
7 size: The number of bits in the bitset
8 """
9 # Main array representing the current state of bits
10 self.bits = ['0'] * size
11 # Complement array (inverse of bits) for O(1) flip operation
12 self.complement = ['1'] * size
13 # Counter to track number of '1' bits in the main array
14 self.ones_count = 0
15
16 def fix(self, idx: int) -> None:
17 """
18 Set the bit at index idx to 1.
19
20 Args:
21 idx: Index of the bit to set to 1
22 """
23 # Only increment counter if bit was previously 0
24 if self.bits[idx] == '0':
25 self.bits[idx] = '1'
26 self.ones_count += 1
27 # Update complement array accordingly
28 self.complement[idx] = '0'
29
30 def unfix(self, idx: int) -> None:
31 """
32 Set the bit at index idx to 0.
33
34 Args:
35 idx: Index of the bit to set to 0
36 """
37 # Only decrement counter if bit was previously 1
38 if self.bits[idx] == '1':
39 self.bits[idx] = '0'
40 self.ones_count -= 1
41 # Update complement array accordingly
42 self.complement[idx] = '1'
43
44 def flip(self) -> None:
45 """
46 Flip all bits in the bitset (0 becomes 1, 1 becomes 0).
47 Achieved in O(1) by swapping main and complement arrays.
48 """
49 # Swap the main array with its complement
50 self.bits, self.complement = self.complement, self.bits
51 # Update the count of ones (total bits minus current ones)
52 self.ones_count = len(self.bits) - self.ones_count
53
54 def all(self) -> bool:
55 """
56 Check if all bits are set to 1.
57
58 Returns:
59 True if all bits are 1, False otherwise
60 """
61 return self.ones_count == len(self.bits)
62
63 def one(self) -> bool:
64 """
65 Check if at least one bit is set to 1.
66
67 Returns:
68 True if at least one bit is 1, False otherwise
69 """
70 return self.ones_count > 0
71
72 def count(self) -> int:
73 """
74 Get the count of bits set to 1.
75
76 Returns:
77 Number of bits currently set to 1
78 """
79 return self.ones_count
80
81 def toString(self) -> str:
82 """
83 Get string representation of the bitset.
84
85 Returns:
86 String containing all bits in order
87 """
88 return ''.join(self.bits)
89
1class Bitset {
2 // Primary bit array storing the actual bitset values
3 private char[] bitArray;
4
5 // Complement array storing the flipped version of bitArray
6 private char[] complementArray;
7
8 // Counter tracking the number of '1' bits in bitArray
9 private int onesCount;
10
11 /**
12 * Initializes a Bitset of given size with all bits initially set to 0
13 * @param size The size of the bitset
14 */
15 public Bitset(int size) {
16 bitArray = new char[size];
17 complementArray = new char[size];
18
19 // Initialize primary array with all zeros
20 Arrays.fill(bitArray, '0');
21
22 // Initialize complement array with all ones (opposite of primary)
23 Arrays.fill(complementArray, '1');
24
25 // Initially no bits are set to 1
26 onesCount = 0;
27 }
28
29 /**
30 * Sets the bit at index idx to 1
31 * @param idx The index of the bit to set
32 */
33 public void fix(int idx) {
34 // Only increment count if bit was previously 0
35 if (bitArray[idx] == '0') {
36 bitArray[idx] = '1';
37 onesCount++;
38 }
39 // Update complement array to maintain inverse relationship
40 complementArray[idx] = '0';
41 }
42
43 /**
44 * Sets the bit at index idx to 0
45 * @param idx The index of the bit to unset
46 */
47 public void unfix(int idx) {
48 // Only decrement count if bit was previously 1
49 if (bitArray[idx] == '1') {
50 bitArray[idx] = '0';
51 onesCount--;
52 }
53 // Update complement array to maintain inverse relationship
54 complementArray[idx] = '1';
55 }
56
57 /**
58 * Flips all bits in the bitset (0 becomes 1, 1 becomes 0)
59 * Achieves O(1) time complexity by swapping arrays
60 */
61 public void flip() {
62 // Swap the primary and complement arrays
63 char[] temp = bitArray;
64 bitArray = complementArray;
65 complementArray = temp;
66
67 // Update count: new ones count = total bits - previous ones count
68 onesCount = bitArray.length - onesCount;
69 }
70
71 /**
72 * Checks if all bits are set to 1
73 * @return true if all bits are 1, false otherwise
74 */
75 public boolean all() {
76 return onesCount == bitArray.length;
77 }
78
79 /**
80 * Checks if at least one bit is set to 1
81 * @return true if at least one bit is 1, false otherwise
82 */
83 public boolean one() {
84 return onesCount > 0;
85 }
86
87 /**
88 * Returns the count of bits set to 1
89 * @return The number of 1s in the bitset
90 */
91 public int count() {
92 return onesCount;
93 }
94
95 /**
96 * Returns string representation of the bitset
97 * @return String containing the bit sequence
98 */
99 public String toString() {
100 return String.valueOf(bitArray);
101 }
102}
103
104/**
105 * Your Bitset object will be instantiated and called as such:
106 * Bitset obj = new Bitset(size);
107 * obj.fix(idx);
108 * obj.unfix(idx);
109 * obj.flip();
110 * boolean param_4 = obj.all();
111 * boolean param_5 = obj.one();
112 * int param_6 = obj.count();
113 * String param_7 = obj.toString();
114 */
115
1class Bitset {
2private:
3 string currentBits; // Current bitset representation
4 string complementBits; // Complement of current bitset (0s become 1s, 1s become 0s)
5 int oneCount; // Count of '1' bits in currentBits
6
7public:
8 /**
9 * Constructor: Initialize a bitset of given size with all bits set to 0
10 * @param size: The number of bits in the bitset
11 */
12 Bitset(int size) {
13 currentBits = string(size, '0'); // Initialize all bits to 0
14 complementBits = string(size, '1'); // Complement starts with all 1s
15 oneCount = 0; // Initially no bits are set
16 }
17
18 /**
19 * Set the bit at given index to 1
20 * @param idx: The index of the bit to set
21 */
22 void fix(int idx) {
23 if (currentBits[idx] == '0') {
24 currentBits[idx] = '1';
25 ++oneCount;
26 }
27 complementBits[idx] = '0'; // Update complement accordingly
28 }
29
30 /**
31 * Set the bit at given index to 0
32 * @param idx: The index of the bit to unset
33 */
34 void unfix(int idx) {
35 if (currentBits[idx] == '1') {
36 currentBits[idx] = '0';
37 --oneCount;
38 }
39 complementBits[idx] = '1'; // Update complement accordingly
40 }
41
42 /**
43 * Flip all bits in the bitset (0 becomes 1, 1 becomes 0)
44 * Achieved in O(1) by swapping current and complement strings
45 */
46 void flip() {
47 swap(currentBits, complementBits);
48 oneCount = currentBits.size() - oneCount; // Update count after flip
49 }
50
51 /**
52 * Check if all bits are set to 1
53 * @return true if all bits are 1, false otherwise
54 */
55 bool all() {
56 return oneCount == currentBits.size();
57 }
58
59 /**
60 * Check if at least one bit is set to 1
61 * @return true if at least one bit is 1, false otherwise
62 */
63 bool one() {
64 return oneCount > 0;
65 }
66
67 /**
68 * Get the count of bits set to 1
69 * @return The number of 1s in the bitset
70 */
71 int count() {
72 return oneCount;
73 }
74
75 /**
76 * Get string representation of the current bitset
77 * @return The bitset as a string of '0's and '1's
78 */
79 string toString() {
80 return currentBits;
81 }
82};
83
84/**
85 * Your Bitset object will be instantiated and called as such:
86 * Bitset* obj = new Bitset(size);
87 * obj->fix(idx);
88 * obj->unfix(idx);
89 * obj->flip();
90 * bool param_4 = obj->all();
91 * bool param_5 = obj->one();
92 * int param_6 = obj->count();
93 * string param_7 = obj->toString();
94 */
95
1// Current bitset representation
2let currentBits: string;
3
4// Complement of current bitset (0s become 1s, 1s become 0s)
5let complementBits: string;
6
7// Count of '1' bits in currentBits
8let oneCount: number;
9
10/**
11 * Initialize a bitset of given size with all bits set to 0
12 * @param size - The number of bits in the bitset
13 */
14function Bitset(size: number): void {
15 // Initialize all bits to 0
16 currentBits = '0'.repeat(size);
17
18 // Complement starts with all 1s
19 complementBits = '1'.repeat(size);
20
21 // Initially no bits are set
22 oneCount = 0;
23}
24
25/**
26 * Set the bit at given index to 1
27 * @param idx - The index of the bit to set
28 */
29function fix(idx: number): void {
30 // Convert strings to arrays for manipulation
31 const currentArray = currentBits.split('');
32 const complementArray = complementBits.split('');
33
34 // Only increment count if bit was previously 0
35 if (currentArray[idx] === '0') {
36 currentArray[idx] = '1';
37 oneCount++;
38 }
39
40 // Update complement accordingly
41 complementArray[idx] = '0';
42
43 // Convert arrays back to strings
44 currentBits = currentArray.join('');
45 complementBits = complementArray.join('');
46}
47
48/**
49 * Set the bit at given index to 0
50 * @param idx - The index of the bit to unset
51 */
52function unfix(idx: number): void {
53 // Convert strings to arrays for manipulation
54 const currentArray = currentBits.split('');
55 const complementArray = complementBits.split('');
56
57 // Only decrement count if bit was previously 1
58 if (currentArray[idx] === '1') {
59 currentArray[idx] = '0';
60 oneCount--;
61 }
62
63 // Update complement accordingly
64 complementArray[idx] = '1';
65
66 // Convert arrays back to strings
67 currentBits = currentArray.join('');
68 complementBits = complementArray.join('');
69}
70
71/**
72 * Flip all bits in the bitset (0 becomes 1, 1 becomes 0)
73 * Achieved in O(1) by swapping current and complement strings
74 */
75function flip(): void {
76 // Swap current and complement bitsets
77 const temp = currentBits;
78 currentBits = complementBits;
79 complementBits = temp;
80
81 // Update count after flip (total bits minus previous count)
82 oneCount = currentBits.length - oneCount;
83}
84
85/**
86 * Check if all bits are set to 1
87 * @returns true if all bits are 1, false otherwise
88 */
89function all(): boolean {
90 return oneCount === currentBits.length;
91}
92
93/**
94 * Check if at least one bit is set to 1
95 * @returns true if at least one bit is 1, false otherwise
96 */
97function one(): boolean {
98 return oneCount > 0;
99}
100
101/**
102 * Get the count of bits set to 1
103 * @returns The number of 1s in the bitset
104 */
105function count(): number {
106 return oneCount;
107}
108
109/**
110 * Get string representation of the current bitset
111 * @returns The bitset as a string of '0's and '1's
112 */
113function toString(): string {
114 return currentBits;
115}
116
Time and Space Complexity
Time Complexity:
__init__(size)
:O(size)
- Creates two arrays of lengthsize
and initializes themfix(idx)
:O(1)
- Only updates a single index in both arrays and potentially updates the counterunfix(idx)
:O(1)
- Only updates a single index in both arrays and potentially updates the counterflip()
:O(1)
- Swaps references between the two arrays and updates the counterall()
:O(1)
- Simple comparison operationone()
:O(1)
- Simple comparison operationcount()
:O(1)
- Returns the counter valuetoString()
:O(size)
- Joins all elements of arraya
into a string
Space Complexity:
- Overall:
O(size)
- The class maintains two arraysa
andb
, each of lengthsize
, plus a constant amount of additional variables (cnt
) - The space is dominated by the two character arrays storing '0' and '1' values
Key Optimization: The clever use of two complementary arrays (a
and b
) allows the flip()
operation to run in O(1)
time by simply swapping references, rather than iterating through all bits which would take O(size)
time. Array b
always maintains the flipped version of array a
, enabling this constant-time flip operation at the cost of doubling the space requirement.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Forgetting to Update the Complement Array
A critical mistake is only updating the main array (self.bits
) in fix()
and unfix()
methods without updating the complement array. This breaks the invariant that the complement array should always be the inverse of the main array.
Incorrect Implementation:
def fix(self, idx: int) -> None:
if self.bits[idx] == '0':
self.bits[idx] = '1'
self.ones_count += 1
# MISSING: self.complement[idx] = '0'
Why This Fails:
When flip()
is called, the arrays are swapped. If the complement wasn't properly maintained, the bitset will have incorrect values after flipping.
Solution: Always update both arrays to maintain the invariant:
def fix(self, idx: int) -> None:
if self.bits[idx] == '0':
self.bits[idx] = '1'
self.ones_count += 1
self.complement[idx] = '0' # Always update complement
2. Incorrect Count Update After Flip
Another common error is forgetting to update or incorrectly calculating ones_count
after a flip operation.
Incorrect Implementation:
def flip(self) -> None:
self.bits, self.complement = self.complement, self.bits
# WRONG: Forgetting to update ones_count
# OR WRONG: self.ones_count = self.ones_count
Why This Fails:
After flipping, all 0s become 1s and vice versa. If there were k
ones before, there are now size - k
ones.
Solution:
def flip(self) -> None:
self.bits, self.complement = self.complement, self.bits
self.ones_count = len(self.bits) - self.ones_count
3. Redundant Counter Updates
Updating the counter even when the bit value doesn't change leads to incorrect counts.
Incorrect Implementation:
def fix(self, idx: int) -> None:
self.bits[idx] = '1'
self.ones_count += 1 # WRONG: Always incrementing
self.complement[idx] = '0'
Why This Fails:
If the bit at idx
is already '1', incrementing the counter again will give an incorrect count.
Solution: Check the current value before updating:
def fix(self, idx: int) -> None:
if self.bits[idx] == '0': # Only update if changing
self.bits[idx] = '1'
self.ones_count += 1
self.complement[idx] = '0'
4. Using Wrong Data Types
Using a list of integers instead of characters might seem natural but can cause issues with string concatenation.
Incorrect Implementation:
def __init__(self, size: int):
self.bits = [0] * size # Using integers
self.complement = [1] * size
def toString(self) -> str:
return ''.join(self.bits) # TypeError: sequence item 0: expected str instance, int found
Solution: Use character arrays or convert properly:
# Option 1: Use characters from the start
self.bits = ['0'] * size
self.complement = ['1'] * size
# Option 2: Convert when needed
def toString(self) -> str:
return ''.join(str(bit) for bit in self.bits)
Which of the following is a min heap?
Recommended Readings
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!