1016. 子串能表示从 1 到 N 数字的二进制串--这道题能学到不少东西

link

直接暴力思路

  1. 根据题意直接枚举[1, n]所有数字代表的二进制字符串
  2. 然后依次判断其是否为 二进制字符串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;
    }
}
  • 虽然成功解决了问题,时间复杂度也说得过去,但是没有体现出该题目的目的性

学习官解

  1. 设区间[l, r]表示大于等于 l l l且小于等于 r r r的整数
  2. 对于 n > 1 n>1 n>1一定存在 k ∈ N + k∈N^+ kN+使得 2 k ≤ n < 2 k + 1 2^k≤n<2^{k+1} 2kn<2k+1
    1. ex:2 存在区间 [2, 4) 内
    2. ex:3 存在区间 [2, 4) 内
    3. ex:10存在区间 [8, 16)内
  3. 那么有,对于 [ 2 k − 1 , 2 k − 1 ] [2^{k-1}, 2^k-1] [2k1,2k1]内的数,它们都小于 n n n,且二进制都表示为 k k k位,这里很好理解
    1. 2 8 − 1 , 2 8 − 1 2^{8-1},2^{8}-1 281,281其二进制表示分别为 1000 00001111 1111总位数都是 8 位
  4. 如果要满足字符串 s s s的子字符串都能表示为数字1到数字 n n n的二进制字符串
    1. 那么字符串 s s s的子字符串也一定能表示数字 [ 2 k − 1 , 2 k − 1 ] [2^{k-1}, 2^k-1] [2k1,2k1]所代表的二进制字符串
    2. 记字符串 s s s的长度为 n n n
    3. 2 4 − 1 , 2 4 − 1 , k = 4 2^{4-1},2^{4}-1,k=4 241,241k=4为例,如果字符串 s s s的子字符串能够表示 其区间内所有的二进制数,那么字符串 s s s的长度应该满足
      1. n ≥ 2 k − 1 + k − 1 n≥2^{k-1} + k - 1 n2k1+k1
      2. 如何求证该结论?写到这里的时候,发现这个点还真的特别不好想出来
      3. 区间 [ 2 k − 1 , 2 k ] [2^{k-1}, 2^k] [2k1,2k]中所有的数的二进制数长度均为 k k k,一共有 2 k − 1 2^{k-1} 2k1
      4. 如果目标字符串要要包含这 2 k − 1 2^{k-1} 2k1个字符串,那么 s s s中至少要有 2 k − 1 2^{k-1} 2k1个长度为 k k k的互不相同的子字符串
      5. 各个不同的字符串之间有重叠部分,那么长度为 k k k的子字符串滑动 ( 2 k − 1 − 1 ) (2^{k-1}-1) (2k11)次(每次滑动长度为1)
      6. 从而得到字符串 s s s的长度应满足 n ≥ 2 k − 1 + k − 1 n≥2^{k-1} + k - 1 n2k1+k1
      7. 但是要注意并不是字符串 s s s的长度满足要求了,就可以得到所有情况的二进制数
  5. 所以可以得到,对于区间 [ 1 , n ] [1, n] [1,n]之间的所有数字,可以分为以下情况
    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+(n2k+1)1=n2k+k1
      1. 解释:区间内一共有 n − 2 k + 1 n-2^k+1 n2k+1个数字,每一个数字的二进制表达长度是 ( k + 1 ) (k+1) (k+1)
      2. 所以,字符串的最小长度为 k + 1 + ( n − 2 k + 1 ) − 1 k+1+(n - 2^k + 1) -1 k+1+(n2k+1)1
    2. 区间 [ 2 k − 1 , 2 k − 1 ] [2^{k-1}, 2^k-1] [2k1,2k1],有 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+(2k12k1+1)1=k+2k11
    3. 以此类推
    4. k = 1 k=1 k=1,此时区间为 [ 1 , 1 ] [1, 1] [1,1]
  6. 题目中给定了 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+2k11) 1000
  7. 假如字符串 s s s的子串能包含 [ 2 k − 1 , 2 k − 1 ] [2^{k-1}, 2^k-1] [2k1,2k1]所有的二进制表示,则一定包含 [ 1 , 2 k − 1 ] [1,2^k-1] [1,2k1]所有的二进制表示
    1. 论证可以将 [ 2 k − 1 , 2 k − 1 ] [2^{k-1},2^k-1] [2k1,2k1]中,每一个数字的二进制表示中的最高位的1去掉,并去掉对应的前导0
    2. 便可以得到 [ 0 , 2 k − 1 − 1 ] [0, 2^{k-1} - 1] [0,2k11]的全部二进制表示
    3. 举例:[4,7]的二进制表示有 100,101,110,111
    4. 去掉高位和前导0--------> 0,1,10,11 ->得到区间[0,3]
  8. 所以对于字符串s仅仅需要判断其是否存在 [ 2 k − 1 , 2 k − 1 ] [2^{k-1}, 2^k-1] [2k1,2k1] [ 2 k , n ] [2^k,n] [2k,n]的全部二进制即可
    1. 这里需要将字符串s的子字符串转换成数字,因为可能存在前导0的情况
  9. 因此可以利用滑动窗口的思想来求解
    1. 分别用长度为 k k k k + 1 k+1 k+1的 “滑动窗口” 来枚举字符串s中全部长度为k和(k+1)的子字符串
    2. 加入哈希表中,判断哈希表中是否存在 [ 2 k − 1 , n ] [2^{k-1},n] [2k1,n]之间的全部数
  10. 以上的分析基于 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

猜你喜欢

转载自blog.csdn.net/GoNewWay/article/details/130663591