优选算法的智慧之光:滑动窗口专题(二)

news2025/3/15 11:08:05

 专栏:算法的魔法世界​​​​​​

个人主页:手握风云

目录

一、例题讲解

1.1. 最大连续1的个数 III

1.2. 找到字符串中所有字母异位词

1.3. 串联所有单词的子串

1.4. 最小覆盖子串


一、例题讲解

1.1. 最大连续1的个数 III

        题目要求是二进制数组,也就是数组中的元素只有0和1。最多翻转k个0,而不是恰好,也就是当数组中0的个数小于k,就不用真的去翻转k个0。

        如果我们按照题目要求解题,代码会非常不好写,因为我们还要去翻转0。我们可以转化一下,我们从数组当中找出一个最长子数组,并且这个子数组中0的个数不超过k个,这样我们就不用再去进行翻转0的操作。

        我们先来思考一下暴力枚举:我们先固定数组当中的第一个元素为起点,然后向后移动,当子数组中0的个数超过k就停止(如下图所示)。我们还需要一个额外的变量zero来统计0的个数。

        接下来根据暴力枚举进行优化。先定义left和right指针指向数组的第一个元素,然后让right指针向后移动。直到left与right所构成的子数组中0的个数大于k。根据暴力枚举的思路,接下来就让left指针也向右一位,right指针也要回退再向右移动。在这段区间内,right还是依旧走到我们原来的位置。

        通过上面的分析,我们发现right指针不需要回退,让left指针直接越过这段区域。这时我们就会发现同向双指针,也就是利用滑动窗口来解决。步骤还是“进窗口→判断→出窗口→更新结果”。进窗口,当right遇到1的时候,无视;遇到0,zero+1。判断,当zero>k时,left向右移动,完成出窗口的操作。

public class Solution {
    public int longestOnes(int[] nums, int k){
        int ret = 0;
        for (int left = 0,right = 0,zero = 0;right < nums.length; right++){
            if(nums[right] == 0) zero++;//进窗口
            while(zero > k){//判断
                if(nums[left++] == 0) zero--;//出窗口
                ret = Math.max(ret,right-left+1);
            }
        }
        return ret;
    }
}

1.2. 找到字符串中所有字母异位词

        我们看下上面的示例1,p可以重新排列为abc、acb、bac、bca、cab、cba。s中的索引则为[0,2]、[6,8],最终输出结果为[0,6]。

        首先我们需要思考如何判断两个字符串为异位词,我们可以利用两个哈希表来统计字符串中字母出现的个数,如果个数相等,则两个字符串为异位词。我们先来思考暴力解法:先把字符串p丢进hash1中,然后从字符串s中找到长度与p相等的子串丢进hash2中,并统计子串中出现的字母个数。

        其实我们从上图中,很容易想到如何对暴力解法进行优化:统计完第一个子串,让里面的字符开始进出哈希表。就像窗口一样从头到尾,并且滑动窗口的长度是固定的,与p的长度相等。

        然后我们就可以利用滑动窗口的步骤来解题:进窗口,把字符丢进hash2中;判断,当窗口的长度大于p的长度时,就要进行出窗口的操作;最后检查两个哈希表中的字符数量是否一致,更新结果。

        由于题目当中的字符串仅包含小写字母,所以我们可以定义一个大小为26的数组来与哈希表判断是否相等,还需要判断进出窗口,这样时间复杂度就为26+n→O(n)。但如果我们遇到更难的题目就可能会超时,我们还需要对最后的更新做优化。

        我们再额外定义一个变量来统计“有效字符”的个数,这个“有效字符”指的是p中的字符。进入后,如果hash2中的有效字符小于hash1中的字符,则count++;出去前,我们需要检查出去的字符是否等于hash1中的字符,则出去的是有效字符。进出窗口的同时维护count的大小。

class Solution {
    public List<Integer> findAnagrams(String s, String p) {
        List<Integer> ret = new ArrayList<Integer>();
        char[] ss = s.toCharArray();
        char[] pp = p.toCharArray();

        int[] hash1 = new int[26];//统计p中每一个字符出现的个数
        for(char ch : pp) hash1[ch - 'a']++;

        int[] hash2 = new int[26];//统计窗口中字符出现的个数
        for (int left = 0,right = 0,count = 0;right < s.length(); right++){
            char in = ss[right];
            if(++hash2[in - 'a'] <= hash1[in - 'a']) count++;//进窗口与维护count
            if(right - left + 1 > p.length()){//判断
                char out = ss[left++];
                if(hash2[out - 'a']-- <= hash1[out - 'a']) count--;//出窗口与维护count
            }
            if(count == p.length()) ret.add(left);
        }
        return ret;
    }
}

1.3. 串联所有单词的子串

        题目要求我们,将字符串数组的子字符串串联,然后在字符串s中的一个字串找出字母异位词。与上一题类似,但这道题面对的是一个一个的字符串,但我们依然可以利用滑动窗口和哈希表来解决。首先在哈希表上,这里需要用到容器来映射字符串和字符串出现的次数;在指针移动上,right指针不能一次移动一个字符,长度应与words里的字符串长度一致。对于滑动窗口的执行次数(如下图),我们只需要执行这3次滑动窗口的操作就可以找出,其他操作都是多余的。所以滑动窗口的执行次数也是字符串数组中字符的长度。

        完整代码实现:

class Solution {
    public List<Integer> findSubstring(String s, String[] words) {
        List<Integer> ret = new ArrayList<>();
        Map<String,Integer> hash1 = new HashMap<String,Integer>();//保存words里面单词的频次
        for(String str : words) hash1.put(str, hash1.getOrDefault(str,0)+1);//把单词丢进哈希表里面,并单词个数
        int len = words[0].length(),m = words.length;
        for (int i = 0; i < len; i++) {//执行次数
            Map<String,Integer> hash2 = new HashMap<String,Integer>();//统计窗口内单词的频次
            for (int left = i,right = i,count = 0;right + len <= s.length();right += len){//count用来统计有效字符串的个数
                //进窗口与维护count
                String in = s.substring(right,right+len);
                hash2.put(in,hash2.getOrDefault(in,0)+1);
                if(hash2.get(in) <= hash1.getOrDefault(in,0)) count++;
                //判断
                if(right - left + 1 > len * m){
                    //出窗口与维护count
                    String out = s.substring(left,left+len);
                    if(hash2.get(out) <= hash1.getOrDefault(out,0)) count--;
                    hash2.put(out,hash2.get(out) - 1);
                    left += len;
                }
                if(count == m) ret.add(left);
            }
        }
        return ret;
    }
}

1.4. 最小覆盖子串

        我们先理清题目要求:题目要我们从字符串s中找到一个最小子串,与字符串t构成包含关系。如何没有这样的子串,返回空字符串“”。

        我们还是先来思考一下暴力解法:先定义两个指针,我们以其中一个指针为起点,另一个指针向右移动,找到所有符合条件的子串,从里面挑出最短的长度。如果转化成代码,依然是借助哈希表, 将遍历过的字符丢进哈希表中进行统计直到里面的字符个数大于等于t中的即可。

        接下来对暴力解法进行一个优化。我们看下图,我们从s中找出一段符合要求的子串,然后让left向后移动一步,此时会出现两种情况,要么缩小的区间还是符合要求,要么不符合要求,我们就让right向右移动,并且在这期间right是不需要回退的。这时候我们就可以用滑动窗口与双指针来解决。

        进窗口,把s中的字符串丢进哈希表中统计。当窗口是合法的时候,判断两个哈希表里的字符个数,再让left向左移动。我们最后是要返回一个字符串,所以们需要知道起始位置和最终位置来决定我们什么时候出窗口。

        如果我们只去遍历一遍哈希表,那么这个算法的时间复杂度是非常高的。我们还需要对算法进行优化。与前两题一样,在定义变量count。只不过这次的count是统计字符的种类,因为在找字母异位词时,子串和字符串是一一对应的关系,这里字符却是大于等于的关系。进窗口之后,比较hash1(in) == hash2(in)(这里之所以不是大于等于,是因为不会统计进重复的子串)。出之前,比较hash1(out) == hash2(out),就能保证出之后窗口不是有效的。因为统计的是字符的种类,所以count = hash1的长度。

{
    public String minWindow(String s,String t){
        char[] ss = s.toCharArray();
        char[] tt = t.toCharArray();

        int[] hash1 = new int[128];//统计字符串t中的频次
        int kinds = 0;//t中有多少种字符
        for(char ch : tt)
            if(hash1[ch]++ == 0) kinds++;
        int[] hash2 = new int[128];
        int minlen = Integer.MAX_VALUE, begin = -1;
        for(int left = 0,right = 0,count = 0;right < ss.length; right++){
            char in = ss[right];
            if(++hash2[in] == hash1[in]) count++;//进窗口与维护count
            while(kinds == count){//判断
                //更新结果
                if(right - left +1 < minlen){
                    begin = left;
                    minlen = right - left + 1;
                }
                char out = ss[left++];
                if(hash2[out]-- == hash1[out]) count--;//出窗口与维护count
            }
        }
        if(begin == -1) return new String();
        else return s.substring(begin,begin+minlen);
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2311174.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【蓝桥杯单片机】第十二届省赛

一、真题 二、模块构建 1.编写初始化函数(init.c) void Cls_Peripheral(void); 关闭led led对应的锁存器由Y4C控制关闭蜂鸣器和继电器 由Y5C控制 2.编写LED函数&#xff08;led.c&#xff09; void Led_Disp(unsigned char ucLed); 将ucLed取反的值赋给P0 开启锁存器…

剑指 Offer II 047. 二叉树剪枝

comments: true edit_url: https://github.com/doocs/leetcode/edit/main/lcof2/%E5%89%91%E6%8C%87%20Offer%20II%20047.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E5%89%AA%E6%9E%9D/README.md 剑指 Offer II 047. 二叉树剪枝 题目描述 给定一个二叉树 根节点 root &#xff0c;树的每…

第5章:vuex

第5章&#xff1a;vuex 1 求和案例 纯vue版2 vuex工作原理图3 vuex案例3.1 搭建vuex环境错误写法正确写法 3.2 求和案例vuex版细节分析源代码 4 getters配置项4.1 细节4.2 源代码 5 mapState与mapGetters5.1 总结5.2 细节分析5.3 源代码 6 mapActions与mapMutations6.1 总结6.2…

视觉在协作机器人上的场景应用

看了UR、ABB等协作机器人公司的一些视觉方面的应用&#xff0c;总结大概有下面几个方面。 1.工业制造领域 3C 产品生产 外观检测&#xff1a;可精确检测电子元件的划痕、污渍、凹陷等外观缺陷&#xff0c;如手机屏幕的微小划痕、芯片表面的瑕疵等&#xff0c;确保产品高质量&a…

MyBatis-Plus开发流程:Spring Boot + MyBatis-Plus 实现对 book_tab 表的增删改查及Redis缓存

前言 MyBatis-Plus 是一个 MyBatis 的增强工具&#xff0c;旨在简化开发、减少工作量。本文将介绍如何使用 Spring Boot 集成 MyBatis-Plus 来操作数据库&#xff0c;并结合 Redis 实现数据的缓存功能。 1项目搭建 1.1 创建 Spring Boot 项目 可以通过 Spring Initializr 快…

从开源大模型工具Ollama存在安全隐患思考企业级大模型应用如何严守安全红线

近日&#xff0c;国家网络安全通报中心通报大模型工具Ollama默认配置存在未授权访问与模型窃取等安全隐患&#xff0c;引发了广泛关注。Ollama作为一款开源的大模型管理工具&#xff0c;在为用户提供便捷的同时&#xff0c;却因缺乏有效的安全管控机制&#xff0c;存在数据泄露…

通过Docker搭个游戏——疯狂大陆(Pkland)

最近在研究我的服务器&#xff0c;在服务器上搭了很多docker的项目&#xff0c;然后找着找着发现一个能用Docker配置环境的游戏叫Pkland。 项目地址&#xff1a;GitHub - popkarthb/pkland: 疯狂大陆是一款多人在线的战略游戏。 游戏操作简捷,您仅需要使用浏览器就可以在任何时…

hive之LEAD 函数详解

1. 函数概述 LEAD 是 Hive 中的窗口函数&#xff0c;用于获取当前行之后指定偏移量处的行的值。常用于分析时间序列数据、计算相邻记录的差异或预测趋势。 2. 语法 LEAD(column, offset, default) OVER ([PARTITION BY partition_column] [ORDER BY order_column [ASC|DESC]…

springboot429-基于springboot的教务管理系统(源码+数据库+纯前后端分离+部署讲解等)

&#x1f495;&#x1f495;作者&#xff1a; 爱笑学姐 &#x1f495;&#x1f495;个人简介&#xff1a;十年Java&#xff0c;Python美女程序员一枚&#xff0c;精通计算机专业前后端各类框架。 &#x1f495;&#x1f495;各类成品Java毕设 。javaweb&#xff0c;ssm&#xf…

linux磁盘非lvm分区

linux磁盘非lvm分区 类似于windows划分C盘、D盘&#xff0c;并且不需要多个磁盘空间合一 图形化直接分区 通过gparted 这个提供直观的图形化分区&#xff0c;类似windows的磁盘管理工具 下载方式&#xff1a; 乌班图/debian系列&#xff1a; sudo apt install gparted红帽…

Linux:文件描述符与重定向

目录 一、文件描述符 1.文件内核对象 2.文件描述符分配原则 二、文件重定向 1.重定向的现象 输出重定向 输入重定向 dup2 2.重定向的使用 三、标准输出和标准错误 继上篇文章中&#xff0c;我们了解了fd打印的值为文件描述符&#xff0c;那么它还有什么作用呢&…

C# 开发工具Visual Studio下载和安装

开发环境与工具 C#的主要开发环境是Visual Studio&#xff0c;这是一个功能强大的集成开发环境&#xff08;IDE&#xff09;&#xff0c;集成了代码编辑、调试、项目管理、版本控制等功能。此外&#xff0c;Visual Studio Code也是一个轻量级的跨平台代码编辑器&#xff0c;支…

网络安全ctf试题 ctf网络安全大赛真题

MISC 1 签到 难度 签到 复制给出的flag输入即可 2 range_download 难度 中等 flag{6095B134-5437-4B21-BE52-EDC46A276297} 0x01 分析dns流量&#xff0c;发现dns && ip.addr1.1.1.1存在dns隧道数据&#xff0c;整理后得到base64: cGFzc3dvcmQ6IG5zc195eWRzIQ 解…

Embedding技术:DeepWalkNode2vec

引言 在推荐系统中&#xff0c;Graph Embedding技术已经成为一种强大的工具&#xff0c;用于捕捉用户和物品之间的复杂关系。本文将介绍Graph Embedding的基本概念、原理及其在推荐系统中的应用。 什么是Graph Embedding&#xff1f; Graph Embedding是一种将图中的节点映射…

基于IMM算法的目标跟踪,四模型IMM|三维环境|4个模型分别是:CV、左转CT、右转CT、CA(基于EKF,订阅专栏后可获得完整源代码)

这段MATLAB代码实现了基于交互多模型(IMM)算法的目标跟踪,结合了四种运动模型(匀速直线、左转圆周、右转圆周和匀加速直线)。通过定义状态方程、生成带噪声的测量数据,以及执行IMM迭代,该代码有效地实现了多模型的状态估计和融合。最终,用户可以通过可视化结果观察目标…

前端开发10大框架深度解析

摘要 在现代前端开发中&#xff0c;框架的选择对项目的成功至关重要。本文旨在为开发者提供一份全面的前端框架指南&#xff0c;涵盖 React、Vue.js、Angular、Svelte、Ember.js、Preact、Backbone.js、Next.js、Nuxt.js 和 Gatsby。我们将从 简介、优缺点、适用场景 以及 实际…

图像形成与计算机视觉基础

1. 图像形成的基本原理 图像形成是物理世界与传感器&#xff08;如胶片、CCD/CMOS&#xff09;交互的过程&#xff0c;核心是光线的传播与记录。 1.1 直接放置胶片模型 物理原理&#xff1a;物体表面反射的光线直接照射到胶片上&#xff0c;但无任何遮挡或聚焦机制。 问题&a…

Spring Boot 缓存最佳实践:从基础到生产的完整指南

Spring Boot 缓存最佳实践&#xff1a;从基础到生产的完整指南 引言 在现代分布式系统中&#xff0c;缓存是提升系统性能的银弹。Spring Boot 通过 spring-boot-starter-cache​ 模块提供了开箱即用的缓存抽象&#xff0c;但如何根据业务需求实现灵活、可靠的缓存方案&#xf…

Ubuntu20.04双系统安装及软件安装(一):系统安装

Ubuntu20.04双系统安装及软件安装&#xff08;一&#xff09;&#xff1a;系统安装 Ubuntu系统卸载Ubuntu20.04安装BIOS进入系统安装 许久没写博客了&#xff0c;今天开始重新回归了。首先记录我在双系统上重装Ubuntu20.04的安装过程记录以及个人见解。 Ubuntu系统卸载 参考双…

Linux14-io多路复用

UDP:单循环服务器,服务器同一时刻只能响应一个客户端的请求 TCP:并发服务器,服务器同一时刻只能响应多个客户端的请求 一、构建TCP并发服务器 让TCP服务端具备同时响应多个客户端的能力。 1.多进程 资源消耗大,同资源平台下,并发量小。 2.多线程 创建线程、进程,比…