179 lines
9.0 KiB
YAML
179 lines
9.0 KiB
YAML
title: Binary String With Substrings Representing 1 To N
|
|
slug: binary-string-with-substrings-representing-1-to-n
|
|
difficulty: medium
|
|
leetcode_id: 1016
|
|
leetcode_url: https://leetcode.com/problems/binary-string-with-substrings-representing-1-to-n/
|
|
categories:
|
|
- strings
|
|
- math
|
|
patterns:
|
|
- slug: sliding-window
|
|
is_optimal: true
|
|
|
|
function_signature: "def query_string(s: str, n: int) -> bool:"
|
|
|
|
test_cases:
|
|
visible:
|
|
- input: { s: "0110", n: 3 }
|
|
expected: true
|
|
- input: { s: "0110", n: 4 }
|
|
expected: false
|
|
hidden:
|
|
- input: { s: "1", n: 1 }
|
|
expected: true
|
|
- input: { s: "0", n: 1 }
|
|
expected: false
|
|
- input: { s: "110101011011000011011111000000", n: 15 }
|
|
expected: false
|
|
- input: { s: "1111000110110111011100011", n: 12 }
|
|
expected: true
|
|
- input: { s: "10101010101010", n: 7 }
|
|
expected: false
|
|
- input: { s: "1010110001010100111010011110100101110", n: 20 }
|
|
expected: false
|
|
|
|
description: |
|
|
Given a binary string `s` and a positive integer `n`, return `true` *if the binary representation of all the integers in the range* `[1, n]` *are **substrings** of* `s`*, or* `false` *otherwise*.
|
|
|
|
A **substring** is a contiguous sequence of characters within a string.
|
|
|
|
constraints: |
|
|
- `1 <= s.length <= 1000`
|
|
- `s[i]` is either `'0'` or `'1'`
|
|
- `1 <= n <= 10^9`
|
|
|
|
examples:
|
|
- input: 's = "0110", n = 3'
|
|
output: "true"
|
|
explanation: "The binary representations are: 1 = '1', 2 = '10', 3 = '11'. All of these are substrings of '0110': '1' appears at index 1, '10' appears at index 2, '11' appears at index 1."
|
|
- input: 's = "0110", n = 4'
|
|
output: "false"
|
|
explanation: "The binary representation of 4 is '100', which is not a substring of '0110'."
|
|
|
|
explanation:
|
|
intuition: |
|
|
At first glance, this problem seems daunting: how can we possibly check all numbers from 1 to n when n can be as large as 10<sup>9</sup>?
|
|
|
|
The key insight is a **length constraint**. A string of length `m` can contain at most `m` substrings of any given length `k` (specifically, `m - k + 1` substrings). Since our string has at most 1000 characters, it can contain at most about 1000 distinct substrings of each length.
|
|
|
|
Think of it like this: if you need to fit all binary representations from 1 to n into a string of length 1000, there's a natural limit. The binary representation of n has roughly `log2(n)` digits. For n = 10<sup>9</sup>, that's about 30 digits. A string of length 1000 can have at most ~970 substrings of length 30, ~971 substrings of length 29, etc.
|
|
|
|
This means if n is too large relative to the string length, we can immediately return `false`. In practice, if n exceeds roughly 2000-3000, the string simply cannot contain enough distinct substrings. This transforms a seemingly impossible O(n) problem into a very manageable one.
|
|
|
|
**Another critical observation**: if a number k appears as a substring, then `k/2` (integer division) also appears. Why? Because `k/2` in binary is just k with the last bit removed. If "10110" is a substring, then "1011" is too (it's the prefix). This means we only need to check the **upper half** of the range `[n/2 + 1, n]`, since the lower half is automatically covered!
|
|
|
|
approach: |
|
|
We solve this using a **String Containment Check with Early Termination**:
|
|
|
|
**Step 1: Handle the length constraint**
|
|
|
|
- If the string is too short to possibly contain all required substrings, we could return `false` early
|
|
- In practice, we let the iteration handle this naturally since it will fail quickly
|
|
|
|
|
|
|
|
**Step 2: Check only the upper half of the range**
|
|
|
|
- Instead of checking all numbers from 1 to n, we only check from `n // 2 + 1` to `n`
|
|
- This is because if binary(k) exists as substring, binary(k // 2) also exists (it's a prefix)
|
|
- This optimization cuts our work in half
|
|
|
|
|
|
|
|
**Step 3: Iterate and check containment**
|
|
|
|
- For each number `i` in the range `[n // 2 + 1, n]`:
|
|
- Convert `i` to its binary representation (without the '0b' prefix)
|
|
- Check if this binary string is a substring of `s`
|
|
- If any number is not found, immediately return `false`
|
|
|
|
|
|
|
|
**Step 4: Return the result**
|
|
|
|
- If all numbers in the upper half are found, return `true`
|
|
- The lower half is guaranteed to exist due to the prefix property
|
|
|
|
common_pitfalls:
|
|
- title: Checking All Numbers From 1 to N
|
|
description: |
|
|
A naive approach might try to check every number from 1 to n:
|
|
|
|
```python
|
|
for i in range(1, n + 1):
|
|
if bin(i)[2:] not in s:
|
|
return False
|
|
```
|
|
|
|
With n up to 10<sup>9</sup>, this would require a billion iterations. Even though each `in` check is O(m) where m is the string length, this is far too slow.
|
|
|
|
The key insight is that we only need to check the upper half `[n/2 + 1, n]` because smaller numbers are automatically covered as prefixes.
|
|
wrong_approach: "Check all n numbers"
|
|
correct_approach: "Only check numbers in [n/2 + 1, n]"
|
|
|
|
- title: Ignoring String Length Constraints
|
|
description: |
|
|
A string of length 1000 can only contain about 1000 distinct substrings of any particular length. If n requires more distinct substrings than the string can possibly hold, the answer is automatically `false`.
|
|
|
|
For example, if n = 10000, we'd need binary strings of length up to 14 bits. But a 1000-character string can only have ~987 substrings of length 14. Since we need ~5000 distinct numbers in just the upper half, it's impossible.
|
|
|
|
In practice, the iteration will fail quickly anyway, but understanding this constraint helps explain why the problem is tractable.
|
|
wrong_approach: "Assume any n is possible"
|
|
correct_approach: "Recognize string length limits the maximum feasible n"
|
|
|
|
- title: Including the '0b' Prefix in Binary Conversion
|
|
description: |
|
|
Python's `bin()` function returns strings like `'0b101'` for the number 5. If you forget to strip the `'0b'` prefix with `bin(i)[2:]`, your substring checks will fail.
|
|
|
|
For example, checking if `'0b11'` is in `'0110'` returns `False`, but checking if `'11'` is in `'0110'` returns `True`.
|
|
wrong_approach: "Use bin(i) directly"
|
|
correct_approach: "Use bin(i)[2:] to strip the prefix"
|
|
|
|
key_takeaways:
|
|
- "**Leverage mathematical properties**: The prefix relationship between binary(k) and binary(k/2) cuts the search space in half"
|
|
- "**Consider physical constraints**: A finite string has a limited number of substrings, which bounds the maximum n that's feasible"
|
|
- "**Early termination**: Return `false` as soon as any required substring is missing"
|
|
- "**This pattern appears in coverage problems**: Checking if a set of items exists within a container, where relationships between items can reduce the search space"
|
|
|
|
time_complexity: "O(n * m) in the worst case, where m is the string length. However, due to the upper-half optimization, we only check n/2 numbers, and each check is O(m). In practice, for large n, the algorithm fails quickly due to string length constraints."
|
|
space_complexity: "O(log n). We create binary string representations of each number, which have length O(log n)."
|
|
|
|
solutions:
|
|
- approach_name: Upper Half Check
|
|
is_optimal: true
|
|
code: |
|
|
def query_string(s: str, n: int) -> bool:
|
|
# Only check upper half: if k is found, k//2 is also found (prefix)
|
|
for i in range(n, n // 2, -1):
|
|
# Convert to binary without '0b' prefix
|
|
binary_rep = bin(i)[2:]
|
|
# Check if this binary representation is a substring
|
|
if binary_rep not in s:
|
|
return False
|
|
return True
|
|
explanation: |
|
|
**Time Complexity:** O(n/2 * m) where m is the string length. We check at most n/2 numbers, and each substring check is O(m).
|
|
|
|
**Space Complexity:** O(log n) for storing the binary representation of each number.
|
|
|
|
We iterate from n down to n/2 + 1, checking if each number's binary representation exists in s. Due to the prefix property of binary numbers, finding all numbers in the upper half guarantees all numbers in the lower half exist too.
|
|
|
|
- approach_name: Brute Force (All Numbers)
|
|
is_optimal: false
|
|
code: |
|
|
def query_string(s: str, n: int) -> bool:
|
|
# Check every number from 1 to n
|
|
for i in range(1, n + 1):
|
|
# Convert to binary without '0b' prefix
|
|
binary_rep = bin(i)[2:]
|
|
# Check if this binary representation is a substring
|
|
if binary_rep not in s:
|
|
return False
|
|
return True
|
|
explanation: |
|
|
**Time Complexity:** O(n * m) where m is the string length. We check all n numbers.
|
|
|
|
**Space Complexity:** O(log n) for storing the binary representation.
|
|
|
|
This naive approach checks every number from 1 to n. While correct, it's twice as slow as the optimized version since it doesn't exploit the prefix property. For very large n, this would be prohibitively slow, though the string length constraint means the loop typically terminates early anyway.
|