LeetCode 717: 1-bit and 2-bit Characters
A clear explanation of determining whether the last character must be a one-bit character using greedy parsing.
Problem Restatement
We are given a binary array:
bits
The array encodes characters using two rules:
| Character Type | Encoding |
|---|---|
| One-bit character | 0 |
| Two-bit character | 10 or 11 |
The array always ends with:
0
We need to determine whether the final character must be a one-bit character.
Return:
True
if the last character is one-bit.
Otherwise return:
False
Input and Output
| Item | Meaning |
|---|---|
| Input | Binary array bits |
| Output | Whether the last character is one-bit |
| One-bit encoding | 0 |
| Two-bit encoding | 10 or 11 |
| Important detail | The array always ends with 0 |
The function shape is:
class Solution:
def isOneBitCharacter(self, bits: list[int]) -> bool:
...
Examples
Example 1:
bits = [1, 0, 0]
Parse from left to right:
10 -> two-bit character
0 -> one-bit character
The final character is one-bit.
Output:
True
Example 2:
bits = [1, 1, 1, 0]
Parse from left to right:
11 -> two-bit character
10 -> two-bit character
The final 0 belongs to the last two-bit character.
So the final character is not one-bit.
Output:
False
First Thought: Try Every Parsing
A recursive approach could try every possible valid decoding.
At each index:
| Current Bit | Possible Action |
|---|---|
0 |
Take one-bit character |
1 |
Take two-bit character |
Then recursively check whether the final character is one-bit.
This works, but it is unnecessary.
The encoding rules already force a unique greedy parsing.
Key Insight
The encoding is deterministic.
Whenever we see:
0
it must represent a one-bit character.
Whenever we see:
1
it must begin a two-bit character, because there is no valid one-bit encoding starting with 1.
So:
| Current Bit | Move |
|---|---|
0 |
Advance by 1 |
1 |
Advance by 2 |
We only need to simulate decoding until we reach the last position.
If we land exactly on the final index, then the last character is one-bit.
If we jump past the final index, then the last 0 was consumed as part of a two-bit character.
Algorithm
- Start with index
i = 0. - While
i < len(bits) - 1:- If
bits[i] == 0, incrementiby1. - Otherwise increment
iby2.
- If
- Return whether:
i == len(bits) - 1
Correctness
The encoding rules uniquely determine how to parse the array.
If bits[i] == 0, the current character must be the one-bit encoding 0, so advancing by one position is correct.
If bits[i] == 1, the current character must be either 10 or 11, both of which are two-bit encodings, so advancing by two positions is correct.
Therefore the algorithm always follows the only valid decoding path.
The loop stops before processing the final array position directly.
If the index ends exactly at the final position, then the final 0 begins its own one-bit character.
If the index jumps beyond the final position, then the final 0 was consumed as the second bit of a two-bit character.
Thus the algorithm returns True exactly when the last character is one-bit.
Complexity
Let n be the length of bits.
| Metric | Value | Why |
|---|---|---|
| Time | O(n) |
We scan the array once |
| Space | O(1) |
Only one index variable is used |
Implementation
class Solution:
def isOneBitCharacter(self, bits: list[int]) -> bool:
i = 0
last = len(bits) - 1
while i < last:
if bits[i] == 0:
i += 1
else:
i += 2
return i == last
Code Explanation
The variable:
last = len(bits) - 1
stores the final index.
We process characters from left to right:
while i < last:
If the current bit is 0, it represents a one-bit character:
if bits[i] == 0:
i += 1
If the current bit is 1, it begins a two-bit character:
else:
i += 2
After parsing finishes:
return i == last
means the last character stands alone as a one-bit character.
Example Walkthrough
Use:
bits = [1, 0, 0]
Initialize:
i = 0
last = 2
At index 0:
bits[0] = 1
So this begins a two-bit character:
10
Advance:
i = 2
Now:
i == last
So the final bit forms a one-bit character.
Return:
True
Now use:
bits = [1, 1, 1, 0]
At index 0:
11
Advance to:
i = 2
At index 2:
10
Advance to:
i = 4
Now:
i > last
So the final 0 was part of the two-bit character 10.
Return:
False
Alternative Observation
Another way to think about the problem:
The final 0 is one-bit if and only if the number of consecutive 1s immediately before it is even.
For example:
[1, 0, 0]
There is one 1 before the final 0.
Odd count means the previous 1 pairs with the earlier 0, leaving the last 0 alone.
But:
[1, 1, 1, 0]
There are three consecutive 1s before the final 0.
Odd count means the final 0 gets consumed by a two-bit character.
The greedy simulation is usually clearer and simpler.
Testing
def test_is_one_bit_character():
s = Solution()
assert s.isOneBitCharacter([1, 0, 0]) is True
assert s.isOneBitCharacter([1, 1, 1, 0]) is False
assert s.isOneBitCharacter([0]) is True
assert s.isOneBitCharacter([1, 0]) is False
assert s.isOneBitCharacter([0, 0]) is True
assert s.isOneBitCharacter([1, 1, 0]) is True
print("all tests passed")
test_is_one_bit_character()
Test coverage:
| Test | Why |
|---|---|
| Standard true example | Confirms final standalone 0 |
| Standard false example | Confirms final 0 consumed by two-bit character |
Single 0 |
Smallest valid input |
| Single two-bit character | Confirms no standalone final character |
| Two one-bit characters | Confirms repeated one-bit parsing |
| Mixed encoding | Confirms greedy stepping works |