1016. Binary String With Substrings Representing 1 To N
Problem Description
You are given a binary string s
and a positive integer n
. Your task is to determine whether the binary representations of all integers from 1 to n (inclusive) appear as substrings within the string s
.
A substring is defined as any contiguous sequence of characters that appears within the string. For example, if s = "0110"
, then "0"
, "1"
, "11"
, "110"
, "01"
, "011"
, and "0110"
are all valid substrings.
The function should return true
if every integer from 1 to n, when converted to its binary representation (without leading zeros), can be found as a substring in s
. Otherwise, return false
.
For example:
-
If
s = "0110"
andn = 3
:- Binary of 1 is
"1"
- found ins
at positions 1 and 2 - Binary of 2 is
"10"
- found ins
starting at position 2 - Binary of 3 is
"11"
- found ins
starting at position 1 - Result:
true
- Binary of 1 is
-
If
s = "0110"
andn = 4
:- Binary of 4 is
"100"
- not found ins
- Result:
false
- Binary of 4 is
The solution leverages two key optimizations:
- If
n > 1000
, it returnsfalse
immediately because a string would need to be exponentially large to contain all binary representations up to such large numbers. - It checks from
n
down ton//2 + 1
because if a numberi
in the range[n//2 + 1, n]
is missing froms
, we can immediately returnfalse
. Numbers in the lower half have shorter binary representations and are more likely to be present if the larger numbers are present.
Intuition
The first key insight is recognizing that as numbers grow larger, their binary representations become longer. For a string s
of length L
to contain all binary representations from 1 to n
, it must have enough "space" to accommodate all these different binary patterns.
Consider the binary representation lengths:
- Numbers from 1 to 1: require 1 bit (
"1"
) - Numbers from 2 to 3: require 2 bits (
"10"
,"11"
) - Numbers from 4 to 7: require 3 bits (
"100"
to"111"
) - Numbers from
2^k
to2^(k+1) - 1
: requirek+1
bits
If n
is very large (like greater than 1000), the string s
would need to contain an enormous number of distinct substrings. The pigeonhole principle tells us that beyond a certain threshold, it becomes impossible for a reasonably-sized string to contain all required binary representations. This explains the if n > 1000: return False
optimization.
The second clever insight involves the checking order. Why check from n
down to n//2 + 1
instead of checking all numbers from 1 to n
?
The reasoning is that larger numbers have longer binary representations and are therefore "harder to fit" or less likely to appear as substrings. If we're missing any number in the range [n//2 + 1, n]
, we can immediately return false
.
Why can we skip checking numbers from 1 to n//2
? Because if all numbers from n//2 + 1
to n
are present as substrings, the smaller numbers (with shorter binary representations) are very likely to be present as well. The binary representations of smaller numbers often appear as parts of larger numbers' representations. For instance, if "110"
(6 in binary) is in the string, then "1"
, "11"
, and "10"
are automatically present too.
This optimization significantly reduces the number of checks needed while maintaining correctness, making the algorithm more efficient.
Learn more about Sliding Window patterns.
Solution Approach
The implementation uses a straightforward but optimized approach to solve this problem:
Step 1: Handle Large Values
if n > 1000: return False
This optimization immediately returns false
for large values of n
. The reasoning is that for n > 1000
, the string s
would need to be impractically large to contain all binary representations. This is based on the mathematical observation that the number of distinct substrings a string can have is bounded by its length.
Step 2: Check Critical Range
return all(bin(i)[2:] in s for i in range(n, n // 2, -1))
This line does several things:
-
Range Selection:
range(n, n // 2, -1)
generates numbers fromn
down ton//2 + 1
in descending order. This checks approximately half of the numbers, specifically the larger half. -
Binary Conversion:
bin(i)[2:]
converts each numberi
to its binary representation and removes the'0b'
prefix that Python adds. For example:bin(5)
returns'0b101'
bin(5)[2:]
returns'101'
-
Substring Check: The
in
operator checks if the binary representation exists as a substring ins
. This is a built-in Python operation that efficiently searches for the substring. -
All Function: The
all()
function returnsTrue
only if every element in the iterator isTrue
. If any binary representation is not found ins
, it immediately returnsFalse
(short-circuit evaluation).
Why This Works:
The algorithm leverages the fact that if all numbers in the range [n//2 + 1, n]
have their binary representations in s
, then it's highly probable that all numbers from 1 to n//2
are also present. This is because:
- Smaller numbers have shorter binary representations
- Shorter binary strings are more likely to appear as substrings
- Many shorter binary strings appear as parts of longer ones
Time Complexity: O(n * |s|) in the worst case, where we check roughly n/2
numbers and each substring check takes O(|s|) time.
Space Complexity: O(1) if we don't count the space used for binary string conversion, which is temporary and small (O(log n) bits).
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 the solution with s = "01101110"
and n = 6
.
Step 1: Check if n > 1000
- Since
n = 6 < 1000
, we continue to the next step.
Step 2: Check numbers from n down to n//2 + 1
We need to check numbers from 6 down to 4 (since 6//2 + 1 = 4):
Checking n = 6:
- Binary of 6:
"110"
- Search in
s = "01101110"
: Found at position 1 → ✓
Checking n = 5:
- Binary of 5:
"101"
- Search in
s = "01101110"
: Found at position 2 → ✓
Checking n = 4:
- Binary of 4:
"100"
- Search in
s = "01101110"
: Not found → ✗
Since binary of 4 is not found, the function returns false
.
Note that we didn't need to check numbers 1, 2, and 3. The algorithm assumes that if the larger numbers (4-6) were all present, the smaller ones would likely be present too. In this case, we found a missing number in the critical range, so we can immediately return false
.
Why this optimization works: If we had checked all numbers 1-6:
- Binary of 1:
"1"
- Found (multiple locations) - Binary of 2:
"10"
- Found at position 2 - Binary of 3:
"11"
- Found at positions 1 and 5 - Binary of 4:
"100"
- Not found - Binary of 5:
"101"
- Found at position 2 - Binary of 6:
"110"
- Found at position 1
The optimization correctly identified that not all numbers are present by only checking the upper half, saving us from checking numbers 1-3.
Solution Implementation
1class Solution:
2 def queryString(self, s: str, n: int) -> bool:
3 # Early termination: if n > 1000, the string cannot contain all binary representations
4 # This is based on the constraint that string length is limited
5 if n > 1000:
6 return False
7
8 # Check if all binary representations from (n//2 + 1) to n exist as substrings in s
9 # We only need to check the upper half because:
10 # - If all numbers from (n//2 + 1) to n are present as binary substrings
11 # - Then all numbers from 1 to n//2 are also present (they appear as prefixes)
12 for i in range(n, n // 2, -1):
13 # Convert number to binary (remove '0b' prefix)
14 binary_representation = bin(i)[2:]
15
16 # Check if this binary string exists as a substring in s
17 if binary_representation not in s:
18 return False
19
20 return True
21
1class Solution {
2 /**
3 * Check if all binary representations of integers from 1 to n exist as substrings in s
4 *
5 * @param s The input string containing binary digits
6 * @param n The upper bound of the range [1, n] to check
7 * @return true if all binary representations exist as substrings, false otherwise
8 */
9 public boolean queryString(String s, int n) {
10 // Optimization: If n is too large, the string cannot contain all binary representations
11 // For n > 1000, the required string length would exceed practical limits
12 if (n > 1000) {
13 return false;
14 }
15
16 // Check only numbers from (n/2 + 1) to n
17 // Key insight: If binary representations of larger numbers exist,
18 // smaller numbers are likely covered as substrings of larger ones
19 for (int currentNumber = n; currentNumber > n / 2; currentNumber--) {
20 // Convert current number to binary string representation
21 String binaryRepresentation = Integer.toBinaryString(currentNumber);
22
23 // Check if the binary representation exists as a substring
24 if (!s.contains(binaryRepresentation)) {
25 return false;
26 }
27 }
28
29 // All checked numbers have their binary representations in the string
30 return true;
31 }
32}
33
1class Solution {
2public:
3 bool queryString(string s, int n) {
4 // Optimization: if n > 1000, it's impossible for string s to contain
5 // all binary representations from 1 to n as substrings
6 // This is based on the constraint that s.length() <= 1000
7 if (n > 1000) {
8 return false;
9 }
10
11 // Key insight: if all numbers from (n/2 + 1) to n exist as substrings,
12 // then all numbers from 1 to n/2 also exist
13 // This is because the binary representation of numbers from 1 to n/2
14 // are prefixes of numbers from (n/2 + 1) to n after removing the leading bit
15
16 // Check if binary representations of numbers from (n/2 + 1) to n exist in s
17 for (int num = n; num > n / 2; --num) {
18 // Convert number to 32-bit binary string
19 string binaryStr = bitset<32>(num).to_string();
20
21 // Remove leading zeros to get the actual binary representation
22 size_t firstOnePos = binaryStr.find_first_not_of('0');
23 binaryStr = binaryStr.substr(firstOnePos);
24
25 // Check if this binary string exists as a substring in s
26 if (s.find(binaryStr) == string::npos) {
27 return false;
28 }
29 }
30
31 // All required binary representations exist as substrings
32 return true;
33 }
34};
35
1/**
2 * Checks if a binary string contains all binary representations of integers from 1 to n
3 * @param s - The binary string to search within
4 * @param n - The upper bound of the range [1, n] to check
5 * @returns true if all binary representations exist in the string, false otherwise
6 */
7function queryString(s: string, n: number): boolean {
8 // Early return for large values of n (optimization based on pigeonhole principle)
9 // A string of length L can contain at most L distinct substrings
10 if (n > 1000) {
11 return false;
12 }
13
14 // Check if binary representations of numbers from n down to n/2 + 1 exist in the string
15 // Key insight: if all numbers from (n/2, n] are present, then all numbers from [1, n/2]
16 // are guaranteed to be present as prefixes of larger numbers
17 for (let currentNumber = n; currentNumber > n / 2; --currentNumber) {
18 // Convert current number to binary representation
19 const binaryRepresentation = currentNumber.toString(2);
20
21 // Check if the binary representation exists as a substring
22 if (s.indexOf(binaryRepresentation) === -1) {
23 return false;
24 }
25 }
26
27 // All required binary representations were found
28 return true;
29}
30
Time and Space Complexity
Time Complexity: O(n * m)
where m
is the length of string s
and n
is the input parameter (bounded by 1000).
- The code iterates from
n
down ton // 2 + 1
, which is approximatelyn/2
iterations - For each iteration
i
, it converts the number to binary usingbin(i)[2:]
, which takesO(log i)
time - Then it checks if this binary string is a substring of
s
using thein
operator, which takesO(m * log i)
time in the worst case (wherelog i
is the length of the binary representation) - Since
i
can be at mostn
, andlog n
is bounded bylog 1000 ≈ 10
(due to the early return), the substring check dominates - Total:
O(n/2 * m * log n)
which simplifies toO(n * m)
sincelog n
is bounded by a constant
Space Complexity: O(log n)
- The
bin(i)[2:]
operation creates a temporary string of lengthO(log i)
for each number - The
all()
function with a generator expression doesn't store all values at once, only processes them one at a time - The maximum space used at any point is for storing the binary representation of a single number, which is
O(log n)
- Since
n
is bounded by 1000, this is effectivelyO(1)
constant space, but more preciselyO(log n)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Assuming the Lower Half is Always Present
The most critical pitfall in this solution is the assumption that if all numbers from [n//2 + 1, n]
are present, then all numbers from [1, n//2]
are automatically present. This is not always true.
Counter-example:
s = "111"
,n = 4
- Binary of 4 is
"100"
- not present (returns False correctly) - But if we had
s = "10011"
,n = 3
:- Binary of 3 is
"11"
- present ✓ - Binary of 2 is
"10"
- present ✓ - Binary of 1 is
"1"
- present ✓ - The optimization would only check 3 and 2, missing that we need to verify 1 as well
- Binary of 3 is
Why this usually works but can fail:
While larger numbers tend to contain patterns of smaller numbers, there's no guarantee. For example, "100"
contains "1"
and "10"
but not "11"
.
2. Incorrect Range Boundary
The code range(n, n // 2, -1)
actually iterates from n
down to n//2 + 1
, not including n//2
. This could miss checking important numbers right at the boundary.
Example Issue:
- When
n = 5
, the range checks[5, 4, 3]
but not2
or1
- If
n//2 = 2
, we're not checking binary of2
which is a critical boundary case
Solution: Complete Check for Safety
class Solution:
def queryString(self, s: str, n: int) -> bool:
# Early termination for large n
if n > 1000:
return False
# Method 1: Check all numbers (most reliable)
for i in range(1, n + 1):
if bin(i)[2:] not in s:
return False
return True
# Alternative Method 2: Optimized but with full verification
# First check the upper half for early termination
for i in range(n, max(1, n // 2), -1):
if bin(i)[2:] not in s:
return False
# Then verify the lower half as well
for i in range(1, min(n // 2 + 1, n + 1)):
if bin(i)[2:] not in s:
return False
return True
3. Edge Cases with Small n
When n
is very small (like 1 or 2), the division n // 2
could lead to unexpected behavior:
- If
n = 1
, thenn // 2 = 0
, andrange(1, 0, -1)
would only check 1 - If
n = 2
, thenn // 2 = 1
, andrange(2, 1, -1)
would only check 2
Better Approach with HashSet for Verification:
class Solution:
def queryString(self, s: str, n: int) -> bool:
if n > 1000:
return False
# Collect all binary substrings of relevant lengths
seen = set()
max_len = len(bin(n)) - 2 # Maximum binary length needed
for i in range(len(s)):
for j in range(1, min(max_len + 1, len(s) - i + 1)):
substring = s[i:i+j]
if substring[0] != '0': # Valid binary (no leading zeros)
seen.add(substring)
# Check if all required numbers are present
for i in range(1, n + 1):
if bin(i)[2:] not in seen:
return False
return True
This approach pre-computes all valid binary substrings and then verifies each required number, avoiding the assumption pitfall entirely.
Which algorithm is best for finding the shortest distance between two points in an unweighted graph?
Recommended Readings
https assets algo monster cover_photos stack svg Sliding Window Maximum Monotonic Stack We have an array and a sliding window defined by a start index and an end index The sliding window moves from left of the array to right There are always k elements in the window The window
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
Want a Structured Path to Master System Design Too? Don’t Miss This!