核心思路与优化策略
力扣 1016 题要求判断二进制字符串 S 是否包含从 1 到 N 所有整数的二进制表示作为子串。直接暴力遍历 1 到 N 并检查子串在 N 较大时会超时,因此需要利用数学性质进行剪枝优化。
关键优化点:
- 上限剪枝 :如果 N \> 2047(即 2\^{11}-1),直接返回
false。因为长度为 L 的二进制串最多包含 L-k+1 个长度为 k 的不同子串。对于 N 很大时,所需的二进制串长度总和远超 S 的最大长度限制(题目中 S.length \\le 1000)。具体而言,若要表示 1 到 2048,至少需要长度为 11 的二进制串 10000000000,而 S 的长度不足以容纳所有必要的组合 。 - 范围缩减:只需检查 \[\\lfloor N/2 \\rfloor + 1, N\] 范围内的数字。如果 S 包含了 x 的二进制串,那么它必然包含 x 右移一位(即 \\lfloor x/2 \\rfloor)的二进制串(因为 x 的二进制串去掉最后一位就是 \\lfloor x/2 \\rfloor 的二进制串)。因此,若 \[\\lfloor N/2 \\rfloor + 1, N\] 均存在,则 \[1, \\lfloor N/2 \\rfloor\] 也必然存在 。
算法实现 (Python)
以下代码实现了上述优化逻辑。首先处理 N 过大的情况,然后仅遍历 N/2 到 N 的数字,将其转换为二进制字符串并在 S中查找。
python
class Solution:
def queryString(self, s: str, n: int) -> bool:
# 优化点 1: 当 N > 2047 时,由于字符串 S 长度限制 (<=1000),不可能包含所有子串
# 2^11 = 2048,二进制长度为 11。若 N 过大,所需子串总长度超过 S 的最大可能容量
if n > 2047:
return False
# 优化点 2: 只需检查 [n//2 + 1, n] 区间
# 原理:如果 x 的二进制是 S 的子串,那么 x>>1 (即 x//2) 的二进制也是 S 的子串
# 只要最大的那一半数字都在,较小的一半自然也在
for i in range(n // 2 + 1, n + 1):
# 将整数转换为二进制字符串 (去掉 '0b' 前缀)
binary_rep = bin(i)[2:]
# 检查是否为子串
if binary_rep not in s:
return False
return True
复杂度分析
| 指标 | 说明 |
|---|---|
| 时间复杂度 | O(\\min(N, K) \\cdot L),其中 K=2047 是剪枝上限,L 是二进制串平均长度。由于 N 被限制在常数范围内,实际复杂度接近 $O(L \cdot |
| 空间复杂度 | O(L),用于存储临时生成的二进制字符串,L 为 \\log_2 N。 |
| 适用场景 | 适用于 S 长度较小 (\\le 1000) 但 N 可能很大的情况,通过数学剪枝避免无效计算 。 |
示例推导
假设输入 S = "0110", N = 3:
- 剪枝检查:3 \\le 2047,继续。
- 确定范围:需检查 \[\\lfloor 3/2 \\rfloor + 1, 3\] 即 \[2, 3\]。
- 执行检查 :
- i=2: 二进制为
"10"。S 中包含"10"吗?是的 (010...这里的索引有点问题,实际上是0110中包含10吗?0110的子串有01,11,10。是的,包含)。 - i=3:二进制为
"11"。S 中包含"11"吗?是的。
- i=2: 二进制为
- 结论 :所有检查通过,返回
true。
假设输入 S = "0110", N = 4:
- 确定范围:需检查 \[\\lfloor 4/2 \\rfloor + 1, 4\] 即 \[3, 4\]。
- 执行检查 :
- i=3:
"11"在 S 中。 - i=4: 二进制为
"100"。S (0110) 中不包含"100"。
- i=3:
- 结论 :返回
false。