LeetCode 1461. 检查一个字符串是否包含所有长度为 K 的二进制子串

LeetCode 1461. 检查一个字符串是否包含所有长度为 K 的二进制子串

题目描述

给你一个二进制字符串 s 和一个整数 k。如果所有长度为 k 的二进制字符串都是 s 的子串,请返回 true,否则返回 false

示例

输入:s = "00110110", k = 2

输出:true

解释:长度为 2 的二进制串有 "00"、"01"、"10"、"11",它们都在 s 中出现过。

解法一:哈希集合存储子串

思路

长度为 k 的二进制子串一共有 2^k 种可能。我们只需要将字符串 s 中所有长度为 k 的子串存入一个哈希集合,最后判断集合大小是否等于 2^k 即可。

剪枝优化

如果字符串的长度连理论上的最小长度都不够,那么肯定无法包含所有子串。包含所有 2^k 个二进制串的最短字符串长度是 (1 << k) + k - 1(即 de Bruijn 序列的长度)。因此可以先检查长度,快速返回 false

代码实现

cpp 复制代码
class Solution {
public:
    bool hasAllCodes(string s, int k) {
        // 长度预判断
        if (s.size() < (1 << k) + k - 1)
            return false;

        unordered_set<string> strs;
        for (int i = 0; i + k <= s.size(); ++i) {
            // 截取长度为 k 的子串并插入集合
            strs.insert(s.substr(i, k));
        }
        return strs.size() == (1 << k);
    }
};

复杂度分析

  • 时间复杂度:O(n·k),其中 n 是字符串长度。需要遍历 O(n) 个子串,每次截取子串和计算哈希需要 O(k) 时间。
  • 空间复杂度:O(2^k·k),哈希集合中最多存储 2^k 个长度为 k 的字符串。

解法二:滑动窗口 + 位运算

思路

将长度为 k 的二进制子串视为一个二进制整数,取值范围为 [0, 2^k - 1]。我们可以通过滑动窗口在 O(1) 时间内计算出每个子串对应的整数值,并存入哈希集合,最后判断集合大小是否为 2^k

滑动窗口更新公式

假设当前窗口对应的整数值为 num(窗口从下标 i 开始)。当窗口向右滑动一位时,新的窗口值为:

复制代码
new_num = (old_num - (leftmost << (k-1))) * 2 + rightmost

其中:

  • leftmost 为移出窗口的最左边字符(转换为 0 或 1)
  • rightmost 为新移入窗口的最右边字符

具体推导:

  • 旧窗口值 num 可以表示为:leftmost * 2^(k-1) + middle,其中 middle 是后 k-1 位的数值。
  • 去掉最高位后的 middle = num - (leftmost << (k-1))
  • middle 左移一位(乘以 2)得到 middle * 2,相当于后 k-1 位整体左移,低位补 0。
  • 加上新移入的 rightmost,得到新窗口值。

代码实现

cpp 复制代码
class Solution {
public:
    bool hasAllCodes(string s, int k) {
        // 长度预判断
        if (s.size() < (1 << k) + k - 1)
            return false;

        // 将前 k 个字符转换为整数
        int num = stoi(s.substr(0, k), nullptr, 2);
        unordered_set<int> exists = {num};

        for (int i = 1; i + k <= s.size(); ++i) {
            // 滑动窗口更新数值
            num = (num - ((s[i - 1] - '0') << (k - 1))) * 2 + (s[i + k - 1] - '0');
            exists.insert(num);
        }
        return exists.size() == (1 << k);
    }
};

复杂度分析

  • 时间复杂度:O(n)。每次窗口滑动只进行常数次位运算和集合插入,总时间复杂度为 O(n)。
  • 空间复杂度:O(2^k)。使用整数集合存储所有可能的数值,每个整数占 4 字节,比存储字符串更节省空间。

两种解法对比

方法 时间复杂度 空间复杂度 适用场景
哈希集合(子串) O(n·k) O(2^k·k) 代码简单,适合 k 较小的情况
滑动窗口+位运算 O(n) O(2^k) 效率更高,k 较大时不易超时

总结 :解法二利用位运算将子串映射为整数,避免了字符串操作,是更优的实现。但两种方法的核心思想相同:统计所有长度为 k 的不同子串,并与总数 2^k 比较。


扩展思考

  • k 较大时(如 k > 20),2^k 可能超过内存限制,但题目通常保证 k ≤ 20,所以可行。
  • 解法一中 s.substr(i, k) 本身返回临时对象,会自动触发移动语义,无需显式使用 move
相关推荐
江畔柳前堤6 小时前
大语言模型分布式训练:从并行策略到万卡工程的系统梳理
人工智能·分布式·深度学习·算法·目标检测·机器学习·语言模型
Forever Nore7 小时前
LeetCode 4 寻找两个正序数组的中位数 - 二分
算法·leetcode
罗西的思考8 小时前
【OpenClaw具身硬件】MiniClaw 阅读笔记---(1)基础
人工智能·算法·机器学习
蛋先生DX9 小时前
大模型参数存储格式揭秘:BF不是男朋友
深度学习·算法·llm
爱跳舞的烤冷面10 小时前
自学嵌入式第22天(数据结构——哈希)
数据结构·算法·哈希算法
猎嘤一号10 小时前
博弈论(Game Theory)的理论、算法与工程
人工智能·算法·安全·博弈论
月光船幽幽10 小时前
影子模式下保护 logits 不被修改
人工智能·python·算法
马拉AI10 小时前
腾讯开源 Agent 记忆系统,AI“换对话就忘”的问题有了新解法(附安装使用教程)
人工智能·算法·开源·科研
地平线开发者12 小时前
征程6工具链模型X86推理方式说明
算法
地平线开发者12 小时前
【征程6】校准量化中HistogramObserver解析
算法