Facebook Pixel

1016. Binary String With Substrings Representing 1 To N

MediumBit ManipulationHash TableStringSliding Window
LeetCode ↗

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" and n = 3:

    • Binary of 1 is "1" - found in s at positions 1 and 2
    • Binary of 2 is "10" - found in s starting at position 2
    • Binary of 3 is "11" - found in s starting at position 1
    • Result: true
  • If s = "0110" and n = 4:

    • Binary of 4 is "100" - not found in s
    • Result: false
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

How We Pick the Algorithm

Why Hash Table / Counting?

This problem maps to Hash Table / Counting through a short path in the full flowchart.

Linkedlist?noFastlookup orcounting?yesHash Table /Counting

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 Flowchart

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 to 2^(k+1) - 1: require k+1 bits

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.

Uses two pointersNew to the pattern? This video walks through two pointers from scratch.
New to patterns?Most interview problems reduce to a handful of patterns. This video covers the main eight.

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:

  1. Range Selection: range(n, n // 2, -1) generates numbers from n down to n//2 + 1 in descending order. This checks approximately half of the numbers, specifically the larger half.

  2. Binary Conversion: bin(i)[2:] converts each number i to its binary representation and removes the '0b' prefix that Python adds. For example:

    • bin(5) returns '0b101'
    • bin(5)[2:] returns '101'
  3. Substring Check: The in operator checks if the binary representation exists as a substring in s. This is a built-in Python operation that efficiently searches for the substring.

  4. Early Exit: If any binary representation is not found in s, the loop immediately returns False. 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 = 6 and 2 * 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
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        // 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}
33
1class 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};
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 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}
30

Time 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 from n down to n // 2 + 1, which is at most m iterations
  • For each iteration i, bin(i)[2:] takes O(log i) time
  • The in check searches s for a pattern of length O(log n), which takes O(m * log n) time in the worst case
  • Total: O(m * m * log m), since log n = O(log m) after the early return

Space Complexity: O(log n)

  • The bin(i)[2:] operation creates a temporary string of length O(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 Roadmap
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Get a Personalized Study Roadmap:

Problem: 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

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More