Facebook Pixel

1016. Binary String With Substrings Representing 1 To N

MediumBit ManipulationHash TableStringSliding Window
Leetcode Link

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

The solution leverages two key optimizations:

  1. If n > 1000, it returns false immediately because a string would need to be exponentially large to contain all binary representations up to such large numbers.
  2. It checks from n down to n//2 + 1 because if a number i in the range [n//2 + 1, n] is missing from s, we can immediately return false. Numbers in the lower half have shorter binary representations and are more likely to be present if the larger numbers are present.
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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

  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. All Function: The all() function returns True only if every element in the iterator is True. If any binary representation is not found in s, it immediately returns False (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 Evaluator

Example 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 to n // 2 + 1, which is approximately n/2 iterations
  • For each iteration i, it converts the number to binary using bin(i)[2:], which takes O(log i) time
  • Then it checks if this binary string is a substring of s using the in operator, which takes O(m * log i) time in the worst case (where log i is the length of the binary representation)
  • Since i can be at most n, and log n is bounded by log 1000 ≈ 10 (due to the early return), the substring check dominates
  • Total: O(n/2 * m * log n) which simplifies to O(n * m) since log n is bounded by a constant

Space Complexity: O(log n)

  • The bin(i)[2:] operation creates a temporary string of length O(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 effectively O(1) constant space, but more precisely O(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

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 not 2 or 1
  • If n//2 = 2, we're not checking binary of 2 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, then n // 2 = 0, and range(1, 0, -1) would only check 1
  • If n = 2, then n // 2 = 1, and range(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.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which algorithm is best for finding the shortest distance between two points in an unweighted graph?


Recommended Readings

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

Load More