717. 1-bit and 2-bit Characters
Problem Description
You are given a binary array bits
that consists of only 0s and 1s, and this array always ends with a 0.
There are two types of special characters that can be encoded in this binary array:
- A one-bit character: represented by a single
0
- A two-bit character: represented by either
10
or11
Your task is to determine whether the last character in the encoded sequence must be a one-bit character (a single 0
).
For example:
- If
bits = [1, 0, 0]
, the array can be decoded as[10, 0]
where10
is a two-bit character and0
is a one-bit character. The last character is a one-bit character, so returntrue
. - If
bits = [1, 1, 1, 0]
, the array can be decoded as[11, 10]
where both are two-bit characters. The last character is not a one-bit character, so returnfalse
.
The key insight is that when we encounter a 1
, it must be the start of a two-bit character (either 10
or 11
), so we need to skip the next bit. When we encounter a 0
, it represents a complete one-bit character. By traversing the array and correctly identifying characters, we can determine if the final 0
stands alone as a one-bit character or is part of a two-bit character.
Intuition
The key observation is that we need to parse the binary array from left to right to understand how the bits group into characters. Since we know the encoding rules:
0
forms a one-bit character10
or11
forms a two-bit character
We notice that whenever we see a 1
, it must be the start of a two-bit character (since there's no valid character starting with 1
that is only one bit). This means when we encounter a 1
, we must consume the next bit as well, regardless of whether it's 0
or 1
.
When we see a 0
, it's a complete one-bit character by itself, so we only move forward by one position.
The clever insight is that we can traverse the array using this rule:
- If the current bit is
0
, move forward by 1 position - If the current bit is
1
, move forward by 2 positions
This can be simplified to: move forward by bits[i] + 1
positions, since when bits[i] = 0
, we move by 0 + 1 = 1
, and when bits[i] = 1
, we move by 1 + 1 = 2
.
By doing this traversal, we're essentially "consuming" characters one by one. If we land exactly at position n - 1
(the last index), it means the last 0
forms its own one-bit character. If we skip over n - 1
and land at n
, it means the last 0
was part of a two-bit character that started at position n - 2
.
This greedy approach works because there's only one valid way to parse the array - we must process characters from left to right, and the rules for identifying character boundaries are deterministic.
Solution Approach
The implementation uses a simple linear traversal with a pointer-based approach:
-
Initialize variables: Start with index
i = 0
and get the length of the arrayn = len(bits)
. -
Traverse the array: Use a while loop that continues as long as
i < n - 1
. We stop before the last element because we want to determine if we land exactly on the last position or skip over it. -
Character consumption logic: Inside the loop, we use the formula
i += bits[i] + 1
:- When
bits[i] = 0
: We have a one-bit character, so we move forward by0 + 1 = 1
position - When
bits[i] = 1
: We have a two-bit character, so we move forward by1 + 1 = 2
positions
- When
-
Final check: After the loop ends, we check if
i == n - 1
:- If
i == n - 1
: We landed exactly on the last position, meaning the last0
is a standalone one-bit character, so returntrue
- If
i == n
: We skipped over the last position (fromn - 2
ton
), meaning the last two bits form a two-bit character, so returnfalse
- If
Example walkthrough with bits = [1, 0, 0]
:
- Start:
i = 0
,n = 3
- Iteration 1:
bits[0] = 1
, soi = 0 + 1 + 1 = 2
- Loop ends since
i = 2
is not less thann - 1 = 2
- Check:
i = 2
equalsn - 1 = 2
, so returntrue
Example walkthrough with bits = [1, 1, 1, 0]
:
- Start:
i = 0
,n = 4
- Iteration 1:
bits[0] = 1
, soi = 0 + 1 + 1 = 2
- Iteration 2:
bits[2] = 1
, soi = 2 + 1 + 1 = 4
- Loop ends since
i = 4
is not less thann - 1 = 3
- Check:
i = 4
does not equaln - 1 = 3
, so returnfalse
The time complexity is O(n)
where n
is the length of the array, as we traverse the array at most once. The space complexity is O(1)
as we only use a constant amount of extra space.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with bits = [1, 1, 0, 1, 0]
:
Initial Setup:
i = 0
(starting position)n = 5
(array length)- We'll traverse until
i < n - 1
(i.e.,i < 4
)
Step-by-step traversal:
Step 1: i = 0
- Current bit:
bits[0] = 1
- This must be the start of a two-bit character
- Move forward by:
bits[0] + 1 = 1 + 1 = 2
positions - New position:
i = 0 + 2 = 2
- Characters identified so far:
[11]
(consumed bits at positions 0 and 1)
Step 2: i = 2
- Current bit:
bits[2] = 0
- This is a one-bit character
- Move forward by:
bits[2] + 1 = 0 + 1 = 1
position - New position:
i = 2 + 1 = 3
- Characters identified so far:
[11, 0]
(consumed bit at position 2)
Step 3: i = 3
- Current bit:
bits[3] = 1
- This must be the start of a two-bit character
- Move forward by:
bits[3] + 1 = 1 + 1 = 2
positions - New position:
i = 3 + 2 = 5
- Characters identified so far:
[11, 0, 10]
(consumed bits at positions 3 and 4)
Loop termination:
- Loop ends because
i = 5
is not less thann - 1 = 4
Final check:
- Is
i == n - 1
? Check if5 == 4
? No. - Since
i = 5
(which equalsn
), we skipped over the last position - This means the last
0
at position 4 was part of the two-bit character10
that started at position 3 - Return:
false
The array was decoded as [11, 0, 10]
, where the last character is a two-bit character, not a one-bit character.
Solution Implementation
1class Solution:
2 def isOneBitCharacter(self, bits: List[int]) -> bool:
3 """
4 Determine if the last character in the bit array must be a 1-bit character.
5
6 Rules:
7 - 1-bit character: represented as '0'
8 - 2-bit characters: represented as '10' or '11'
9
10 Args:
11 bits: List of integers (0s and 1s) representing encoded characters
12
13 Returns:
14 True if the last character must be a 1-bit character, False otherwise
15 """
16 # Initialize pointer and get array length
17 current_index = 0
18 array_length = len(bits)
19
20 # Process all characters except potentially the last one
21 while current_index < array_length - 1:
22 # If current bit is 0: it's a 1-bit character (move 1 position)
23 # If current bit is 1: it's start of a 2-bit character (move 2 positions)
24 # This works because bits[i] + 1 gives us:
25 # - 0 + 1 = 1 (move 1 position for 1-bit character)
26 # - 1 + 1 = 2 (move 2 positions for 2-bit character)
27 current_index += bits[current_index] + 1
28
29 # If we land exactly on the last position (array_length - 1),
30 # it means the last bit forms a 1-bit character
31 return current_index == array_length - 1
32
1class Solution {
2 /**
3 * Determines if the last character must be a one-bit character.
4 * Characters can be represented as:
5 * - One-bit character: 0
6 * - Two-bit character: 10 or 11
7 *
8 * @param bits Array of bits ending with a 0
9 * @return true if the last character is a one-bit character, false otherwise
10 */
11 public boolean isOneBitCharacter(int[] bits) {
12 int currentIndex = 0;
13 int arrayLength = bits.length;
14
15 // Traverse through the array, skipping characters based on their bit length
16 // Stop before the last element to check if it's a standalone one-bit character
17 while (currentIndex < arrayLength - 1) {
18 // If current bit is 1, it forms a two-bit character (10 or 11), skip 2 positions
19 // If current bit is 0, it's a one-bit character, skip 1 position
20 // bits[currentIndex] + 1 gives us: 1 + 1 = 2 for two-bit, 0 + 1 = 1 for one-bit
21 currentIndex += bits[currentIndex] + 1;
22 }
23
24 // If we land exactly at the last position (arrayLength - 1),
25 // it means the last bit is a standalone one-bit character
26 return currentIndex == arrayLength - 1;
27 }
28}
29
1class Solution {
2public:
3 bool isOneBitCharacter(vector<int>& bits) {
4 // Initialize pointer and get array size
5 int i = 0;
6 int n = bits.size();
7
8 // Traverse through the bits array until we reach or pass the second-to-last position
9 // If current bit is 0: it's a 1-bit character, move forward by 1 (0 + 1)
10 // If current bit is 1: it's a 2-bit character, move forward by 2 (1 + 1)
11 while (i < n - 1) {
12 i += bits[i] + 1;
13 }
14
15 // If we landed exactly on the last position (n-1),
16 // it means the last character is a 1-bit character
17 return i == n - 1;
18 }
19};
20
1/**
2 * Determines if the last character must be a one-bit character (0)
3 * Characters can be represented as:
4 * - One-bit character: 0
5 * - Two-bit character: 10 or 11
6 *
7 * @param bits - Array of bits representing encoded characters
8 * @returns true if the last character is a one-bit character, false otherwise
9 */
10function isOneBitCharacter(bits: number[]): boolean {
11 // Initialize pointer to traverse the bits array
12 let i: number = 0;
13
14 // Store the length of the bits array
15 const n: number = bits.length;
16
17 // Traverse the array until we reach the second-to-last position
18 // We stop at n-1 because we want to check if we land exactly on the last bit
19 while (i < n - 1) {
20 // If current bit is 1, it forms a two-bit character (10 or 11), so skip 2 positions
21 // If current bit is 0, it's a one-bit character, so skip 1 position
22 // This is achieved by adding (bits[i] + 1) to i:
23 // - When bits[i] = 1: move by 1 + 1 = 2 positions
24 // - When bits[i] = 0: move by 0 + 1 = 1 position
25 i += bits[i] + 1;
26 }
27
28 // If we land exactly on the last position (n-1), it means the last character
29 // is a one-bit character (0). Otherwise, we've gone past it as part of a
30 // two-bit character
31 return i === n - 1;
32}
33
Time and Space Complexity
Time Complexity: O(n)
where n
is the length of the bits array. The algorithm uses a single while loop that traverses through the array. In each iteration, the pointer i
advances by either 1 or 2 positions (depending on bits[i]
value). In the worst case, when all bits are 0, the pointer advances one position at a time, requiring at most n-1
iterations. In the best case, when bits alternate optimally, the pointer might skip positions, but it still visits each position at most once. Therefore, the time complexity is linear.
Space Complexity: O(1)
. The algorithm only uses a constant amount of extra space for the variables i
and n
, regardless of the input size. No additional data structures are created that scale with the input size.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Incorrectly Processing the Entire Array
The Mistake: Many developers instinctively write a loop that processes every element in the array:
# INCORRECT approach
def isOneBitCharacter(self, bits: List[int]) -> bool:
i = 0
while i < len(bits): # Processing ALL elements
if bits[i] == 0:
i += 1
else:
i += 2
# Now what? How do we know if the last bit was standalone?
Why It's Wrong:
Once you've consumed all bits, you lose the information about whether the last 0
was processed as a standalone one-bit character or as part of a two-bit character. You can't determine the answer after processing everything.
The Solution:
Stop processing before the last element (i < len(bits) - 1
), then check where your pointer landed. This preserves the crucial information about how the last bit should be interpreted.
Pitfall 2: Overthinking with Flag Variables
The Mistake: Trying to track the "type" of the last processed character using additional variables:
# OVERLY COMPLEX approach
def isOneBitCharacter(self, bits: List[int]) -> bool:
i = 0
last_was_one_bit = False
while i < len(bits):
if bits[i] == 0:
last_was_one_bit = True
i += 1
else:
last_was_one_bit = False
i += 2
return last_was_one_bit
Why It's Problematic:
- Adds unnecessary complexity and state management
- More prone to bugs when handling edge cases
- Harder to reason about correctness
The Solution:
The elegant approach relies on pointer positioning alone. By stopping before the last element and checking if i == len(bits) - 1
, you naturally determine whether the last bit is standalone without any extra bookkeeping.
Pitfall 3: Misunderstanding the Two-Bit Character Rule
The Mistake:
Assuming you need to check what comes after a 1
to validate if it forms a valid two-bit character:
# UNNECESSARY validation
def isOneBitCharacter(self, bits: List[int]) -> bool:
i = 0
while i < len(bits) - 1:
if bits[i] == 1:
# Checking if next bit exists and forms valid 10 or 11
if i + 1 < len(bits) and bits[i + 1] in [0, 1]:
i += 2
else:
return False # Invalid encoding?
else:
i += 1
return i == len(bits) - 1
Why It's Wrong:
The problem guarantees that the input is a valid encoding. When you see a 1
, it's guaranteed to be the start of a valid two-bit character (10
or 11
). The next bit is guaranteed to exist and be valid.
The Solution:
Trust the problem constraints. When you encounter a 1
, simply skip two positions without validation. This simplifies the code and avoids unnecessary checks.
Which of the following uses divide and conquer strategy?
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!