1-bit and 2-bit Characters
EasyPrompt
We have two special characters:
- The first character can be represented by one bit
0. - The second character can be represented by two bits (
10or11).
Given a binary array bits that ends with 0, return true if the last character must be a one-bit character.
Example 1:
Input: bits = [1,0,0]
Output: true
Explanation: The only way to decode it is two-bit character and one-bit character.
So the last character is one-bit character.Example 2:
Input: bits = [1,1,1,0]
Output: false
Explanation: The only way to decode it is two-bit character and two-bit character.
So the last character is not one-bit character.
Constraints:
1 <= bits.length <= 1000bits[i]is either0or1.
Approaches
3 approaches with complexity analysis and trade-offs.
This approach simulates the decoding process recursively. A helper function determines if the end of the array can be reached from a given starting index. The decoding rules are applied to determine the next index to recurse on.
Algorithm
- Define a recursive helper function
canDecode(bits, index). - In
canDecode:- If
index == bits.length - 1, returntrue. - If
index >= bits.length, returnfalse. - If
bits[index] == 0, it's a one-bit character, so recursively callcanDecode(bits, index + 1). - If
bits[index] == 1, it's a two-bit character, so recursively callcanDecode(bits, index + 2).
- If
- The initial call from the main function is
canDecode(bits, 0).
Walkthrough
This method uses recursion to mimic the deterministic decoding process. We define a function that takes the current index as a parameter and explores the single possible decoding path from there.
-
The base cases for the recursion are crucial:
- If the current index is the last index of the array (
bits.length - 1), it means we have successfully landed on the final0. This must be a one-bit character, so we've found a valid decoding ending with a one-bit character. We returntrue. - If the current index goes beyond the array bounds (
>= bits.length), it means the previous step led to an invalid state (e.g., a two-bit character starting at the second-to-last position). We returnfalse.
- If the current index is the last index of the array (
-
The recursive step depends on the value at the current index:
- If
bits[index]is0, we must treat it as a one-bit character and move to the next index (index + 1). - If
bits[index]is1, we must treat it as a two-bit character and skip the next bit, moving toindex + 2.
- If
The initial call will be with index = 0.
class Solution { public boolean isOneBitCharacter(int[] bits) { return canDecode(bits, 0); } private boolean canDecode(int[] bits, int index) { // Base case: If we land exactly on the last index, it must be the 1-bit '0'. if (index == bits.length - 1) { return true; } // Base case: If we overshoot, it's an invalid decoding. if (index >= bits.length) { return false; } // Recursive step based on the character type if (bits[index] == 0) { // 1-bit character, move to the next index. return canDecode(bits, index + 1); } else { // 2-bit character, skip the next index. return canDecode(bits, index + 2); } }}Complexity
Time
O(N), where N is the length of the `bits` array. Each recursive call processes one or two elements and moves forward, so each element is visited at most once.
Space
O(N), where N is the length of the array. In the worst-case scenario (an array of all zeros), the recursion depth can go up to N, consuming space on the call stack.
Trade-offs
Pros
Conceptually simple and directly models the problem's structure.
The logic is a straightforward translation of the decoding rules.
Cons
Uses O(N) space for the recursion call stack, which is less efficient than an iterative solution.
Can lead to a
StackOverflowErrorfor very large inputs, although not an issue with the given constraints.
This approach iteratively simulates the decoding process from left to right using a pointer. The pointer is advanced by 1 or 2 steps depending on the bit encountered, mimicking the consumption of one-bit or two-bit characters. The final position of the pointer determines if the last character is one-bit.
Algorithm
- Initialize a pointer
i = 0. - Loop while
i < bits.length - 1. - Inside the loop, if
bits[i]is1, incrementiby 2. - Otherwise (if
bits[i]is0), incrementiby 1. - After the loop terminates, the final position of
idetermines the result. Returntrueifi == bits.length - 1, andfalseotherwise.
Walkthrough
Instead of recursion, we can use a simple loop and a pointer to track our position in the array. This avoids the overhead of the call stack and is generally more efficient in terms of space.
The logic is as follows:
- We start a pointer
iat the beginning of the array (i = 0). - We iterate through the array as long as we haven't reached the last element (
i < bits.length - 1). - At each position
i, we checkbits[i]:- If
bits[i] == 0, we have a one-bit character, so we advance the pointer by 1 (i += 1). - If
bits[i] == 1, we have a two-bit character, so we advance the pointer by 2 (i += 2).
- If
- The loop stops when
iis eitherbits.length - 1orbits.length. - If the final value of
iisbits.length - 1, it means the decoding process perfectly landed on the last element, which must be the one-bit character0. We returntrue. - If the final value of
iisbits.length, it means the last character processed was a two-bit one that ended atbits.length - 1, so the final0was part of it. We returnfalse.
class Solution { public boolean isOneBitCharacter(int[] bits) { int i = 0; while (i < bits.length - 1) { if (bits[i] == 1) { i += 2; } else { i += 1; } } return i == bits.length - 1; }}Complexity
Time
O(N), where N is the length of `bits`. We perform a single pass through the array.
Space
O(1), as we only use a constant amount of extra space for the pointer variable.
Trade-offs
Pros
Highly efficient with O(1) space complexity.
Easy to understand and implement.
Avoids potential stack overflow issues of recursion.
Cons
May perform more operations than the parity check approach in cases where the number of trailing ones is small.
This is a more clever approach that works backward from the second to last element. It relies on the observation that the decoding of the final part of the string depends on the parity of the number of consecutive 1s just before the final 0.
Algorithm
- Get the length of the array,
n. - Initialize a counter
ones = 0. - Iterate backwards from
i = n - 2down to0. - As long as
bits[i]is1, increment theonescounter and continue moving left. - If
bits[i]is0, stop the iteration. - After the loop, check if the final count of
onesis even. Returntrueif it is, andfalseotherwise.
Walkthrough
This optimized approach focuses only on the tail of the array. The key insight is that whether the last 0 is a character by itself depends on how the preceding sequence of 1s is decoded.
Let's consider the block of consecutive 1s right before the final 0. The array looks like ...X, 1, 1, ..., 1, 0, where there are k ones.
- When the decoder reaches the start of this block, it will process the
kones. - If
kis an even number, the1s can be grouped intok/2pairs of11(or10if the last pair includes the final0, but the logic holds). After processing these pairs, the decoder's position will be exactly at the final0, making it a one-bit character. For example,...0, 1, 1, 0->...0,11,0. - If
kis an odd number, after groupingk-1ones into pairs, a single1remains. This1must pair with the final0to form a10two-bit character. For example,...0, 1, 0->...0,10.
Therefore, the problem simplifies to counting the number of consecutive 1s ending at index n-2. If this count is even, the answer is true; if it's odd, the answer is false.
class Solution { public boolean isOneBitCharacter(int[] bits) { int n = bits.length; int ones = 0; // Count the number of consecutive 1s from the second to last element backwards. for (int i = n - 2; i >= 0 && bits[i] == 1; i--) { ones++; } // If the number of consecutive 1s is even, the last character is 1-bit. return ones % 2 == 0; }}Complexity
Time
O(k), where `k` is the number of trailing `1`s before the final `0`. In the worst case, this is O(N) if the array consists of N-1 ones followed by a zero. However, it is much faster on average than a full linear scan.
Space
O(1), as we only use a constant amount of extra space for the counter.
Trade-offs
Pros
Potentially the fastest approach on average, as it only inspects the end of the array.
Very efficient with O(1) space complexity.
An elegant solution based on a clever observation.
Cons
The logic is less direct and might be harder to come up with initially compared to simulation.
Solutions
Solution
class Solution {public boolean isOneBitCharacter(int[] bits) { int i = 0, n = bits.length; while (i < n - 1) { i += bits[i] + 1; } return i == n - 1; }}/** * @param {number[]} bits * @return {boolean} */ var isOneBitCharacter = function (bits) { let i = 0; const n = bits.length; while (i < n - 1) { i += bits[i] + 1; } return i == n - 1; };class Solution {public: bool isOneBitCharacter(vector<int> &bits) { int i = 0, n = bits.size(); while (i < n - 1) i += bits[i] + 1; return i == n - 1; }};class Solution: def isOneBitCharacter(self, bits: List[int]) -> bool: i, n = 0, len(bits) while i < n - 1: i += bits[i] + 1 return i == n - 1Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.