《算法题讲解指南:优选算法-滑动窗口》--15.串联所有单词的子串,16.最小覆盖子串

《算法题讲解指南:优选算法-滑动窗口》--15.串联所有单词的子串,16.最小覆盖子串

🔥小叶-duck个人主页

❄️个人专栏《Data-Structure-Learning》

《C++入门到进阶&自我学习过程记录》《算法题讲解指南》--从优选到贪心

未择之路,不须回头
已择之路,纵是荆棘遍野,亦作花海遨游


目录

15. 串联所有单词的子串

题目链接:

题目描述:

题目示例:

解法(滑动窗口+哈希表):

算法思路:

C++算法代码:

算法总结及流程解析:

16. 最小覆盖子串

题目链接:

题目描述:

题目示例:

解法 (滑动窗口+哈希表):

算法思路:

算法流程:

C++算法代码:

算法总结及流程解析:

结束语


15. 串联所有单词的子串

题目链接:

30. 串联所有单词的子串 - 力扣(LeetCode)

题目描述:

题目示例:

解法(滑动窗口+哈希表):

算法思路:
  • 如果我们把每一个单词看成一个个字母,问题就变成了找到【字符串中所有的字母的异位词】。无非就是之前处理的对象是一个个字符,我们这里处理的对象是一个一个的单词。

有以下几点不同:

  • 哈希表需要使用 hash<string,int>
  • left 和 right 移动的步长是单词的长度(len)
  • 滑动窗口执行的次数也是 len 次

C++算法代码:

class Solution { public: vector<int> findSubstring(string s, vector<string>& words) { //解法:滑动窗口 + 哈希表(容器) vector<int> ret; unordered_map<string, int> hash1; //统计 words 中所有单词出现的频次 //string表示存放的数据是string类型,int表示数据存放的次数 for(auto e : words) { hash1[e]++; } int len = words[0].size(); //len为 words 中每个字符串的长度 int m = words.size(); //m为 words 的长度,目的是为了和 count 相比较 int left = 0; int right = 0; for(int i = 0; i < len; i++) { unordered_map<string, int> hash2;//存放s中的子串 //放到第一个for循环中目的是:每轮判断完成后hash2需要进行重置 //避免上一轮hash2中存放的数据对第二轮造成影响 int count = 0;//统计窗口中“有效字符串”的个数 left = i; //for(right = i; right <= (int)s.size() - len; right += len) //写成上面s.size() - len的形式一定要注意size()返回的类型是size_t //当size_t类型与int类型进行运算时,结果会类型转换出size_t类型 //如果此时s.size() - len为负数则会变成最大值,导致substr越界访问right //所以我们需要先将s.size()返回类型强转成int类型才能进行运算 for(right = i; right + len <= s.size(); right += len) { string str1 = s.substr(right, len); hash2[str1]++; //进窗口 if(hash2[str1] <= hash1[str1]) { count++; } if(right - left + 1 > len * m) //判断 { string str2 = s.substr(left, len); if(hash2[str2] <= hash1[str2]) { count--; } hash2[str2]--; //出窗口 left += len; } if(count == m) //更新结果 { ret.push_back(left); } } } return ret; } };

算法总结及流程解析:

16. 最小覆盖子串

题目链接:

76. 最小覆盖子串 - 力扣(LeetCode)

题目描述:

题目示例:

解法 (滑动窗口+哈希表):

算法思路:

      研究对象是连续的区间,因此可以尝试使用滑动窗口的思想来解决。

      如何判断当前窗口的所有字符是符合要求的呢?

  • 我们可以使用两个哈希表。其中一个将目标串的信息统计起来,另一个哈希表动态的维护窗口内字符串的信息。
  • 当动态哈希表中包含目标串中所有字符,并且对应的个数都不小于目标串的哈希表中各个字符的个数,那么当前的窗口就是一种可行的方案。
算法流程:

  1. 定义两个全局的哈希表:hash1用来记录子串的信息,hash2用来记录目标串 t 的信息;

  2. 实现一个接口函数,判断当前窗口是否满足要求:

    2.1 遍历两个哈希表中对应位置的元素

      2.1.1 如果 t 中某个字符的数量大于窗口中字符的数量,也就是 hash2 某个位置大于 hash1 。说明不匹配,返回 false。

      2.1.2 如果全部匹配,返回 true。

主函数中:

  1.先将 t 的信息放入 hash2 中;

  2.初始化一些变量:左右指针:left = 0,right = 0;目标子串的长度:len = INT_MAX;目标子串的起始位置:begin;(通过目标子串的起始位置和长度,我们就可以找到结果)

  3.当 right 小于字符串 s 的长度时,一直下列循环:

    3.1 将当前遍历到的元素扔进 hash1 中;

    3.2 检测当前窗口是否满足条件;

      3.2.1 如果满足条件:

                        判断当前窗口是否变小。如果变小:更新长度 len,以及字符串的起始位置 begin

                        判断完毕后,将左侧的元素滑出窗口,顺便更新到 hash1;

                        重复上述两个过程,直到窗口不满足条件;

    3.3 right++,遍历下一个元素;

  4. 判断 len 的长度是否等于 INT_MAX;

    4.1 如果相等,说明没有匹配,返回空串;

    4.2 如果不相等,说明匹配,返回 s 中从 retleft 位置往后 len 长度的字符串。

C++算法代码:

class Solution { public: string minWindow(string s, string t) { // //解法一:滑动窗口 + 哈希表(使用容器),count 标记“有效字符”的个数 // unordered_map<char, int> hash1; // for(auto c : t) // { // hash1[c]++; // } // int count = 0; int left = 0; int right = 0; // int n = t.size(); int min_len = s.size() + 1; int begin = -1; // string ret; // unordered_map<char, int> hash2; // for(right = 0; right < s.size(); right++) // { // hash2[s[right]]++; //进窗口 // if(hash2[s[right]] <= hash1[s[right]]) // { // count++; // } // while(hash2[s[left]] > hash1[s[left]]) //判断 // { // hash2[s[left]]--; //出窗口 // left++; // } // if(count == n) // { // if(min_len > right - left + 1) //更新结果 // { // min_len = right - left + 1; // begin = left; // } // } // } // if(begin != -1) // { // ret = s.substr(begin, min_len); // } // return ret; //优化:数组模拟实现哈希表,count 标记“有效字符”的种类 int hash1[128] = { 0 }; //统计字符串 t 中每个字符出现的频次 int kinds = 0; //统计“有效字符”有多少种 for(auto c : t) { if(hash1[c]++ == 0) { kinds++; } } int left = 0; int right = 0; int count = 0; int min_len = s.size() + 1; int begin = -1; string ret; int hash2[128] = { 0 }; for(right = 0; right < s.size(); right++) { hash2[s[right]]++; //进窗口 if(hash2[s[right]] == hash1[s[right]]) { count++; } while(count == kinds)//判断 { if(min_len > right - left + 1) //更新结果 { min_len = right - left + 1; begin = left; } if(hash2[s[left]] == hash1[s[left]]) { count--; } hash2[s[left]]--; //出窗口 left++; } } if(begin != -1) { ret = s.substr(begin, min_len); } return ret; } };

算法总结及流程解析:

结束语

       到此,15.串联所有单词的子串,16.最小覆盖子串 这两道算法题就讲解完了。《串联所有单词的子串》采用滑动窗口 + 哈希表方法,将单词视为字符处理,通过控制窗口步长和频次统计实现高效匹配;《最小覆盖子串》通过双哈希表动态维护目标串和窗口字符频次,优化版使用128位数组提升效率。希望大家能有所收获!

Read more

【Python篇】PyQt5 超详细教程——由入门到精通(序篇)

【Python篇】PyQt5 超详细教程——由入门到精通(序篇)

文章目录 * PyQt5 超详细入门级教程 * 前言 * 序篇:1-3部分:PyQt5基础与常用控件 * 第1部分:初识 PyQt5 和安装 * 1.1 什么是 PyQt5? * 1.2 在 PyCharm 中安装 PyQt5 * 1.3 在 PyCharm 中编写第一个 PyQt5 应用程序 * 1.4 代码详细解释 * 1.5 在 PyCharm 中运行程序 * 1.6 常见问题排查 * 1.7 总结 * 第2部分:创建 PyQt5 应用程序与布局管理 * 2.1 PyQt5 的基本窗口结构

By Ne0inhk
macOS 彻底卸载 Python 的完整指南

macOS 彻底卸载 Python 的完整指南

macOS 彻底卸载 Python 的完整指南 * macOS 彻底卸载 Python 的完整指南 * ⚠️ 重要警告 * 🔍 卸载前检查 * 🗑️ 卸载方法(按安装方式) * 1. 卸载 Homebrew 安装的 Python * 2. 卸载官方 pkg 安装的 Python * 3. 卸载 pyenv 管理的 Python * 4. 卸载 Miniconda/Anaconda * 🧹 全面清理残留文件 * 🔄 恢复系统默认 Python 环境 * 💡 最佳实践:使用虚拟环境 * ⚠️ 特殊情况处理 * 📊 卸载后验证 macOS 彻底卸载 Python 的完整指南 在 macOS 上安全卸载 Python 需要谨慎操作,因为系统自带 Python

By Ne0inhk
【Python】基础语法入门(一)

【Python】基础语法入门(一)

前言 Python作为一门入门门槛低、生态丰富的编程语言,Python早已成为编程初学者、数据分析从业者、后端开发者的首选工具之一。而掌握Python的第一步,就是吃透最核心的基础语法,常量与表达式、变量与类型、注释、输入输出及运算符。今天,我们就结合实例,手把手带你入门这些必备知识点,助你快速搭建Python语法框架。 一、常量与表达式 刚接触 Python 时,我们可以先把它当作一个功能强大的计算器 ,通过简单的表达式,以完成各类算术运算,比如简单的加减乘除,甚至复杂的乘方运算,都能直接通过“表达式”实现。 核心知识点: 1. 表达式与常量:形如1 + 2 * 3的算式称为“表达式”,运算结果为“表达式的返回值”;1、2、3这类固定值称为“字面值常量”,+、-、*、/则是“运算符”。 2. 运算规则:遵循“先乘除后加减”的数学逻辑,

By Ne0inhk
Python 基础与环境配置

Python 基础与环境配置

第一篇:Python 基础与环境配置 学习目标 💡 掌握 Python 语言的基本语法和编程思想 💡 学会安装和配置 Python 开发环境 💡 理解并熟练运用 Python 的数据类型、变量和运算符 💡 掌握 Python 的流程控制语句(条件判断、循环) 💡 学会使用 Python 的函数和模块 💡 了解 Python 的常用开发工具和集成开发环境(IDE) 💡 具备编写简单 Python 程序的能力 重点内容 * Python 语言的发展历程与特点 * Python 开发环境的安装与配置 * Python 的基本语法(变量、数据类型、运算符) * 流程控制语句(if 语句、for 循环、while 循环) * 函数的定义、调用和参数传递 * 模块和包的使用 * 常用开发工具和

By Ne0inhk