1461. Check if a String Contains all Binary Codes of Size K - Explanation
Description
Given a binary string s and an integer k, return true if every binary code of length k is a substring of s. Otherwise, return false.
Example 1:
Input: s = "00110110", k = 2
Output: trueExplanation: The binary codes of length 2 are "00", "01", "10" and "11". They can be all found as substrings at indices 0, 1, 3 and 2 respectively.
Example 2:
Input: s = "0110", k = 1
Output: trueExplanation: The binary codes of length 1 are "0" and "1", it is clear that both exist as a substring.
Example 3:
Input: s = "0110", k = 2
Output: falseExplanation: The binary code "00" is of length 2 and does not exist in the array.
Constraints:
1 <= s.length <= 500,000s[i]is either'0'or'1'.1 <= k <= 20
Topics
Prerequisites
Before attempting this problem, you should be comfortable with:
- Hash Sets - Storing and checking unique substrings efficiently
- Sliding Window - Processing fixed-size windows over a string without redundant computation
- Bit Manipulation - Using bitwise operations to represent binary strings as integers
- Binary Number Representation - Converting between binary strings and decimal integers
1. Brute Force
Intuition
The most straightforward approach is to generate all possible binary codes of length k and check if each one exists as a substring in the given string s. There are exactly such binary codes (from 0 to ), and we need to verify that every single one appears somewhere in s.
Algorithm
- If the length of
sis less than , returnfalseimmediately since there cannot be enough substrings. - Iterate through all numbers from
0to . - For each number, convert it to its binary representation with exactly
kdigits (padding with leading zeros if needed). - Check if this binary string exists as a substring in
s. - If any binary code is not found, return
false. - If all binary codes are found, return
true.
class Solution:
def hasAllCodes(self, s: str, k: int) -> bool:
n = len(s)
if n < (1 << k):
return False
for num in range(1 << k):
binaryCode = format(num, f'0{k}b')
if binaryCode not in s:
return False
return TrueTime & Space Complexity
- Time complexity:
- Space complexity:
Where is the length of the string and is the length of the binary code.
2. Hash Set
Intuition
Instead of checking each possible binary code one by one, we can flip the approach: extract all substrings of length k from s and store them in a set. If the set contains exactly unique substrings, then all binary codes are present. This is more efficient because we process each substring only once.
Algorithm
- If the length of
sis less than , returnfalseimmediately. - Create an empty hash set to store unique substrings.
- Iterate through
sand extract every substring of lengthk. - Add each substring to the hash set.
- After processing all substrings, check if the set size equals .
- Return
trueif the count matches,falseotherwise.
class Solution:
def hasAllCodes(self, s: str, k: int) -> bool:
if len(s) < 2 ** k:
return False
codeSet = set()
for i in range(len(s) - k + 1):
codeSet.add(s[i:i + k])
return len(codeSet) == 2 ** kTime & Space Complexity
- Time complexity:
- Space complexity:
Where is the length of the string and is the length of the binary code.
3. Sliding Window
Intuition
We can improve upon the hash set approach by using bit manipulation to represent each substring as an integer. Instead of storing strings, we maintain a sliding window of k bits. As we move through the string, we update the integer representation by removing the leftmost bit and adding the new rightmost bit. This converts string operations into faster bitwise operations.
Algorithm
- If the length of
sis less than , returnfalse. - Create a boolean array of size to track which codes have been seen.
- Build the initial window by reading the first
kcharacters and converting them to an integer using bit shifts. - Mark the first code as seen and initialize a counter.
- Slide the window one character at a time:
- Remove the leftmost bit using XOR if it was
1. - Shift the current value left by
1. - Add the new rightmost bit using OR.
- If this code has not been seen before, mark it and increment the counter.
- Remove the leftmost bit using XOR if it was
- Return
trueif the counter equals .
class Solution:
def hasAllCodes(self, s: str, k: int) -> bool:
n = len(s)
if n < (1 << k):
return False
codeSet = [False] * (1 << k)
cur = 0
i = j = 0
bit = k - 1
while j < k:
if s[j] == '1':
cur |= (1 << bit)
bit -= 1
j += 1
have = 1
codeSet[cur] = True
while j < n:
if s[i] == '1':
cur ^= (1 << (k - 1))
i += 1
cur <<= 1
if s[j] == '1':
cur |= 1
j += 1
if not codeSet[cur]:
have += 1
codeSet[cur] = True
return have == (1 << k)Time & Space Complexity
- Time complexity:
- Space complexity:
Where is the length of the string and is the length of the binary code.
4. Sliding Window (Optimal)
Intuition
This approach simplifies the sliding window technique by using a bitmask to automatically handle the window size. Instead of explicitly removing the leftmost bit, we use a bitwise AND with a mask () after shifting. This mask keeps only the rightmost k bits, effectively removing any bits that overflow beyond the window size.
Algorithm
- If the length of
sis less than , returnfalse. - Create a boolean array of size to track seen codes.
- Initialize the current code value and a counter for unique codes found.
- Iterate through each character in
s:- Shift the current value left by
1. - Apply a bitmask (AND with ) to keep only the last
kbits. - Add the current character's bit value using OR.
- Once we have processed at least
kcharacters, check if this code is new. - If new, mark it as seen and increment the counter.
- Shift the current value left by
- Return
trueif the counter equals .
class Solution:
def hasAllCodes(self, s: str, k: int) -> bool:
n = len(s)
if n < (1 << k):
return False
codeSet = [False] * (1 << k)
cur = 0
have = 0
for i in range(n):
cur = ((cur << 1) & ((1 << k) - 1)) | (ord(s[i]) - ord('0'))
if i >= k - 1:
if not codeSet[cur]:
codeSet[cur] = True
have += 1
return have == (1 << k)Time & Space Complexity
- Time complexity:
- Space complexity:
Where is the length of the string and is the length of the binary code.
Common Pitfalls
Forgetting the Early Length Check
If the string length is less than k + 2^k - 1, it's impossible to contain all binary codes since there aren't enough substrings. Skipping this check leads to unnecessary computation and potential incorrect results.
# Always check first:
if len(s) < (1 << k):
return FalseOff-by-One Error in Substring Extraction
When iterating to extract substrings of length k, the loop should go from 0 to len(s) - k inclusive. Using range(len(s) - k) instead of range(len(s) - k + 1) misses the last valid substring.
# Wrong: for i in range(len(s) - k)
# Correct:
for i in range(len(s) - k + 1):
codeSet.add(s[i:i + k])Incorrect Bitmask in Sliding Window
When using the sliding window approach with bit manipulation, forgetting to mask the result after shifting causes the integer to grow beyond k bits. The mask (1 << k) - 1 must be applied to keep only the last k bits.
# Wrong: cur = (cur << 1) | bit
# Correct:
cur = ((cur << 1) & ((1 << k) - 1)) | bit
Sign in to join the discussion