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 insat positions 1 and 2 - Binary of 2 is
"10"- found insstarting at position 2 - Binary of 3 is
"11"- found insstarting 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
How We Pick the Algorithm
Why Hash Table / Counting?
This problem maps to Hash Table / Counting through a short path in the full flowchart.
For each i from one to n, slide a window of its bit-length over s and check membership in a hash set of seen substrings.
Open in FlowchartIntuition
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^kto2^(k+1) - 1: requirek+1bits
If n is large compared to s, the string cannot hold all the required patterns. Every number in the range (n/2, n] has a binary length of either L or L - 1, where L is the bit length of n. A single starting position in s can begin at most one of them: if the length-L window starting there is a number at most n, its length-(L - 1) prefix equals that number divided by 2, which is at most n/2 and therefore outside the range. So the n - n/2 numbers in that range need at least n - n/2 different starting positions, which forces n - n/2 <= len(s). When n > 2 * len(s) this fails, which explains the if n > 2 * len(s): return False check. It also keeps the loop short, since n is at most 2 * len(s) once the check passes.
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?
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? For any k in [1, n//2], the number 2k is at most n, and the binary form of 2k is the binary form of k followed by a 0. So k appears in s whenever 2k does. Doubling repeatedly, k, 2k, 4k, ... eventually lands in (n/2, n], and every number in that chain has the binary form of k as a prefix. If all numbers in (n/2, n] are present, every smaller number is present too. For instance, if "110" (6 in binary) is in the string, then "11" (3) and "1" (1) are present as prefixes.
This cuts the number of checks in half without losing correctness.
Pattern 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 > 2 * len(s):
return False
This check immediately returns false for large values of n. Each starting position in s can begin at most one of the numbers in (n/2, n], so those n - n/2 numbers need at least n - n/2 starting positions. When n > 2 * len(s), there are not enough positions.
Step 2: Check Critical Range
for i in range(n, n // 2, -1):
if bin(i)[2:] not in s:
return False
return True
This line does several things:
-
Range Selection:
range(n, n // 2, -1)generates numbers fromndown ton//2 + 1in descending order. This checks approximately half of the numbers, specifically the larger half. -
Binary Conversion:
bin(i)[2:]converts each numberito 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
inoperator checks if the binary representation exists as a substring ins. This is a built-in Python operation that efficiently searches for the substring. -
Early Exit: If any binary representation is not found in
s, the loop immediately returnsFalse. If the loop finishes, every checked number was found.
Why This Works:
For any k in [1, n//2], the binary form of 2k is the binary form of k with a 0 appended, and 2k <= n. Repeatedly doubling k gives a number in [n//2 + 1, n] whose binary form starts with the binary form of k. So if every number in the upper half is a substring of s, every number in the lower half is a substring as well, because it is a prefix of one of them.
Time Complexity: O(|s|^2 * log |s|) in the worst case. After the first check, n <= 2 * |s|, so we check at most |s| numbers, and each substring search for a pattern of length O(log n) takes O(|s| * log n) 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).
Example Walkthrough
Let's walk through the solution with s = "01101110" and n = 6.
Step 1: Check if n > 2 * len(s)
- Since
n = 6and2 * len(s) = 16, 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. If the larger numbers (4-6) were all present, the smaller ones would be present too, since 1, 2, and 3 are prefixes of 4, 4, and 6. In this case, we found a missing number in the upper half, 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, 4, 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: each start position in s can begin at most one number in (n/2, n],
4 # so if n - n//2 > len(s) (guaranteed when n > 2 * len(s)), some number is missing
5 if n > 2 * len(s):
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
211class 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 // Each start position in s can begin at most one number in (n/2, n],
11 // so for n > 2 * s.length() some number in that range must be missing
12 if (n > 2 * s.length()) {
13 return false;
14 }
15
16 // Check only numbers from (n/2 + 1) to n
17 // Every k <= n/2 is a prefix of some number in (n/2, n] (keep doubling k),
18 // so checking the upper half also covers the lower half
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}
331class Solution {
2public:
3 bool queryString(string s, int n) {
4 // Each start position in s can begin at most one number in (n/2, n],
5 // so for n > 2 * s.size() some number in that range must be missing
6 // (this also keeps the loop below short)
7 if (n > 2 * (int) s.size()) {
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 each k <= n/2 is a prefix of 2k, 4k, ...,
14 // and one of those lies in (n/2, n]
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};
351/**
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 n: each start position can begin at most one number in (n/2, n],
9 // so for n > 2 * s.length some number in that range must be missing
10 if (n > 2 * s.length) {
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}
30Time and Space Complexity
Time Complexity: O(m^2 * log m) where m is the length of string s.
- If
n > 2 * m, the function returns immediately - Otherwise
n <= 2 * m, and the loop runs fromndown ton // 2 + 1, which is at mostmiterations - For each iteration
i,bin(i)[2:]takesO(log i)time - The
incheck searchessfor a pattern of lengthO(log n), which takesO(m * log n)time in the worst case - Total:
O(m * m * log m), sincelog n = O(log m)after the early return
Space Complexity: O(log n)
- The
bin(i)[2:]operation creates a temporary string of lengthO(log i)for each number - Only one such string exists at a time
Pattern Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Doubting the Upper-Half Shortcut
It can look unsafe to check only [n//2 + 1, n], but the shortcut is exact, not a heuristic. For any k <= n//2, the binary form of 2k is the binary form of k followed by 0. Doubling k until it exceeds n//2 gives a number in [n//2 + 1, n] whose binary form starts with the binary form of k. So any s that contains the upper half also contains every smaller number. Checking all of 1..n gives the same answer, only with about twice as many checks.
2. Skipping the Early Exit for Large n
The constraints allow n up to 10^9. Without an early return, the loop range(n, n // 2, -1) would run hundreds of millions of times before failing. The check n > 2 * len(s) is safe because each starting position in s can begin at most one number from (n/2, n]: a length-L window that is at most n has a prefix of length L - 1 equal to half its value, which is at most n/2. So those n - n//2 numbers need that many starting positions.
3. Allowing Leading Zeros in Binary Strings
Binary representations must not have leading zeros. When building binary strings manually (for example with a fixed-width formatter such as bitset<32> in C++ or format(i, '032b') in Python), strip the leading zeros before searching. Searching for "00000011" instead of "11" would return false for s = "0110", n = 3, even though 1, 2, and 3 are all present.
Ready to land your dream job?
Unlock your dream job with a 5-minute quiz for a personalized study roadmap!
Get My RoadmapProblem: Given a list of tasks and a list of requirements, compute a sequence of tasks that can be performed, such that we complete every task once while satisfying all the requirements.
Which of the following method should we use to solve this problem?
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 If you prefer videos here's a video that explains recursion in a fun and easy way 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 him
Want a Structured Path to Master System Design Too? Don’t Miss This!