直接暴力思路
- 根据题意直接枚举
[1, n]
所有数字代表的二进制字符串 - 然后依次判断其是否为 二进制字符串s 的子串就可以了
class Solution {
public boolean queryString(String s, int n) {
while(n > 0) {
// 使用 Integer 自带的工具类,将数字转换为二进制字符串
String num_str = Integer.toBinaryString(n);
if(!s.contins(num_str)) {
return false;
}
n--;
}
return true;
}
}
- 虽然成功解决了问题,时间复杂度也说得过去,但是没有体现出该题目的目的性
学习官解
- 设区间
[l, r]
表示大于等于 l l l且小于等于 r r r的整数 - 对于 n > 1 n>1 n>1一定存在 k ∈ N + k∈N^+ k∈N+使得 2 k ≤ n < 2 k + 1 2^k≤n<2^{k+1} 2k≤n<2k+1
ex:2 存在区间 [2, 4) 内
ex:3 存在区间 [2, 4) 内
ex:10存在区间 [8, 16)内
- 那么有,对于 [ 2 k − 1 , 2 k − 1 ] [2^{k-1}, 2^k-1] [2k−1,2k−1]内的数,它们都小于 n n n,且二进制都表示为 k k k位,这里很好理解
- 2 8 − 1 , 2 8 − 1 2^{8-1},2^{8}-1 28−1,28−1其二进制表示分别为
1000 0000
和1111 1111
总位数都是 8 位
- 2 8 − 1 , 2 8 − 1 2^{8-1},2^{8}-1 28−1,28−1其二进制表示分别为
- 如果要满足字符串 s s s的子字符串都能表示为数字1到数字 n n n的二进制字符串
- 那么字符串 s s s的子字符串也一定能表示数字 [ 2 k − 1 , 2 k − 1 ] [2^{k-1}, 2^k-1] [2k−1,2k−1]所代表的二进制字符串
- 记字符串 s s s的长度为 n n n
- 以 2 4 − 1 , 2 4 − 1 , k = 4 2^{4-1},2^{4}-1,k=4 24−1,24−1,k=4为例,如果字符串 s s s的子字符串能够表示 其区间内所有的二进制数,那么字符串 s s s的长度应该满足
- n ≥ 2 k − 1 + k − 1 n≥2^{k-1} + k - 1 n≥2k−1+k−1
- 如何求证该结论?写到这里的时候,发现这个点还真的特别不好想出来
- 区间 [ 2 k − 1 , 2 k ] [2^{k-1}, 2^k] [2k−1,2k]中所有的数的二进制数长度均为 k k k,一共有 2 k − 1 2^{k-1} 2k−1个
- 如果目标字符串要要包含这 2 k − 1 2^{k-1} 2k−1个字符串,那么 s s s中至少要有 2 k − 1 2^{k-1} 2k−1个长度为 k k k的互不相同的子字符串
- 各个不同的字符串之间有重叠部分,那么长度为 k k k的子字符串滑动 ( 2 k − 1 − 1 ) (2^{k-1}-1) (2k−1−1)次(每次滑动长度为1)
- 从而得到字符串 s s s的长度应满足 n ≥ 2 k − 1 + k − 1 n≥2^{k-1} + k - 1 n≥2k−1+k−1
- 但是要注意并不是字符串 s s s的长度满足要求了,就可以得到所有情况的二进制数
- 所以可以得到,对于区间 [ 1 , n ] [1, n] [1,n]之间的所有数字,可以分为以下情况
- 区间 [ 2 k , n ] [2^k,n] [2k,n],此时字符串的长度应该满足 n ≥ k + 1 + ( n − 2 k + 1 ) − 1 = n − 2 k + k − 1 n \geq\ k+1+(n - 2^k + 1) -1=n-2^k+k-1 n≥ k+1+(n−2k+1)−1=n−2k+k−1
- 解释:区间内一共有 n − 2 k + 1 n-2^k+1 n−2k+1个数字,每一个数字的二进制表达长度是 ( k + 1 ) (k+1) (k+1)
- 所以,字符串的最小长度为 k + 1 + ( n − 2 k + 1 ) − 1 k+1+(n - 2^k + 1) -1 k+1+(n−2k+1)−1
- 区间 [ 2 k − 1 , 2 k − 1 ] [2^{k-1}, 2^k-1] [2k−1,2k−1],有 n ≥ k + ( 2 k − 1 − 2 k − 1 + 1 ) − 1 = k + 2 k − 1 − 1 n \geq\ k + (2^k-1-2^{k-1}+1)-1=k+2^{k-1}-1 n≥ k+(2k−1−2k−1+1)−1=k+2k−1−1
- 以此类推
- 当 k = 1 k=1 k=1,此时区间为 [ 1 , 1 ] [1, 1] [1,1]
- 区间 [ 2 k , n ] [2^k,n] [2k,n],此时字符串的长度应该满足 n ≥ k + 1 + ( n − 2 k + 1 ) − 1 = n − 2 k + k − 1 n \geq\ k+1+(n - 2^k + 1) -1=n-2^k+k-1 n≥ k+1+(n−2k+1)−1=n−2k+k−1
- 题目中给定了 1 ≤ s . l e n g t h ( ) ≤ 1000 1 \leq\ s.length() \leq\ 1000 1≤ s.length()≤ 1000,所以,由此有 ( k + 2 k − 1 − 1 ) ≤ 1000 (k+2^{k-1}-1) \leq\ 1000 (k+2k−1−1)≤ 1000
- 假如字符串 s s s的子串能包含 [ 2 k − 1 , 2 k − 1 ] [2^{k-1}, 2^k-1] [2k−1,2k−1]所有的二进制表示,则一定包含 [ 1 , 2 k − 1 ] [1,2^k-1] [1,2k−1]所有的二进制表示
- 论证可以将 [ 2 k − 1 , 2 k − 1 ] [2^{k-1},2^k-1] [2k−1,2k−1]中,每一个数字的二进制表示中的最高位的1去掉,并去掉对应的前导0
- 便可以得到 [ 0 , 2 k − 1 − 1 ] [0, 2^{k-1} - 1] [0,2k−1−1]的全部二进制表示
- 举例:
[4,7]的二进制表示有 100,101,110,111
- 去掉高位和前导0
--------> 0,1,10,11 ->得到区间[0,3]
- 所以对于字符串
s
仅仅需要判断其是否存在 [ 2 k − 1 , 2 k − 1 ] [2^{k-1}, 2^k-1] [2k−1,2k−1]和 [ 2 k , n ] [2^k,n] [2k,n]的全部二进制即可- 这里需要将字符串
s
的子字符串转换成数字,因为可能存在前导0的情况
- 这里需要将字符串
- 因此可以利用滑动窗口的思想来求解
- 分别用长度为 k k k和 k + 1 k+1 k+1的 “滑动窗口” 来枚举字符串
s
中全部长度为k和(k+1)
的子字符串 - 加入哈希表中,判断哈希表中是否存在 [ 2 k − 1 , n ] [2^{k-1},n] [2k−1,n]之间的全部数
- 分别用长度为 k k k和 k + 1 k+1 k+1的 “滑动窗口” 来枚举字符串
- 以上的分析基于 n > 1 n>1 n>1,当 n = 1 n=1 n=1仅需判断字符串
s
中是否含有'1'
即可
my god 我的天,是真滴复杂
class Solution {
public boolean queryString(String s, int n) {
// 如果 n == 1 直接判断字符串 s 中是否含有 字符 1 即可
if(n == 1) {
return s.indexOf('1') > -1;
}
// 因为字符串 s 只含 0 或 1,那么这里需要判断数字 n 最接近的二进制数字
// n最大为 n = 10^9 = (2*5)^9 < (2*8)^9 = 2^21 所以 n 的二进制表示最大不超过 2^21
int k = 21;
while((1 << k) >= n) {
k--;
}
int m = s.length();
// 此时数字n的范围在 [1, 2^k - 1] [2^k, n]
// 也可以 划分为 [2^{k-1}, 2^k - 1] [2^k, n]
// [2^{k-1}, 2^k - 1] 的实际意义
// |-在该区间内的所有的数的二进制表示都是 k 位
// |-一共有 2^k - 1 - 2^{k-1} + 1 个数
// |-所有,字符串 s 的长度至少要为 k + 2^{k-1} - 1 即 m >= k + 2^{k-1} -1
// |-同理,对于区间 [2^k, n] 所有二进制字符都是 k+1 位,共有 n-2^k-1 个数
// 所以 要满足 m >= k + 1 + n - 2^k - 1 - 1 即 m >= n + k - 2^k - 1
// 如果不满足直接返回false即可
// m < k + (1 << (k-1)) - 1 m < n + k - (1 << k) - 1
if((m < k + (1 << (k-1)) - 1) || (m < n + k - (1 << k) - 1)) {
return false;
}
// 长度满足了,接下来分别判断 在区间 [2^{k-1}, 2^k - 1] [2^k, n] 是否满足题目条件,
return check(s, k, 1 << (k - 1), (1 << k) - 1) && check(s, k+1, 1 << k, n);
}
// 在字符串 s 中,寻找所有长度
private boolean check(String s, int k, int mi, int ma) {
Set<Integer> set = new HashSet<>();
// 滑动窗口 计算所有长度为 k 的 (二进制)子字符串 所代表的 十进制数字
int t = 0;
for(int i = 0;i < s.length();i++) {
// 计算长度 为 k 的子字符串
t = t * 2 + s.charAt(i) - '0';
// 超过窗口长度了,减去首个字符所代表的 数
if(i >= k) {
t -= (s.charAt(i - k) - '0') << k;
}
if(i >= k-1 && t >= mi && t <= ma) {
set.add(t);
}
}
return set.size() == ma - mi + 1;
}
}
参考
https://leetcode.cn/problems/binary-string-with-substrings-representing-1-to-n/solution/zi-chuan-neng-biao-shi-cong-1-dao-n-shu-ojtz8/
https://leetcode.cn/problems/binary-string-with-substrings-representing-1-to-n/solution/san-chong-suan-fa-cong-bao-li-dao-you-hu-nmtq