算法通过村第十六关-滑动窗口|白银笔记|经典题目讲解

文章目录

  • 前言
  • 最长字串专场
    • 无重复字符的最长字串
    • 至多包含两个不同字串的最长子串
    • 至多包含K个不同字串的最长子串
  • 长度最小的子数组
  • 盛水最多的容器
  • 寻找字串异位词(排序)
    • 字符串的排序
    • 找到字符串中所有字母异位
  • 总结


前言


提示:所有的话语都颇为类似,而沉默则千差万别。 --卢梭

趁热打铁,我们继续来研究一些高频、热门的滑动窗口问题。

最长字串专场

先看最高频的推荐题目⭐⭐⭐⭐:

3. 无重复字符的最长子串 - 力扣(LeetCode)

滑动窗口—至多包含两个不同字符的最长子串(leetcode 159)_珠穆拉玛峰的博客-CSDN博客

LeetCode算法日记:340.至多包含K个不同字符的最长子串_算法 至多-CSDN博客

看这题目,我们要怎么处理呢?这种造题是不是自己拿手的,顺着这个思路是不是可以造出很多题目来,总结出一些方法,解滑动窗口的模板呢?

无重复字符的最长字串

参考题目地址:3. 无重复字符的最长子串 - 力扣(LeetCode)

在这里插入图片描述

在这里插入图片描述

要找到最长的子串,必然要知道无重复字符串的首部和尾部,然后再从中确定最长的那个,因此至少需要两个指针才可以,这也是滑动窗口思想。即使用滑动窗口,深入分析会发现,具体处理来由很多方式。这里介绍一种经典的使用Map的思考。

我们这里定义一个K—V形式的map,key表示的是当前正在访问的字符串,value是器下标索引值。我们每访问一个新元素,都将其下标更新成对应的索引值。

在这里插入图片描述
如果已经出现过,就和上图的做法一致,abcabc,当第二次出现a的时候,我们就更新left成为第一个b的所在位置,此时惊奇的事情发生了,left要移动的位置恰好就是map.get(‘a’)+1= 1,我们将‘a’用序列来表示,放在一起就是map.get(s.charAt(i)) + 1。其他情况可以类似考虑,参考上图见解。

当然这里有一种特殊的情况,例如abba,我们再次访问b时,left = map.get(‘b’) + 1 = 2。然后继续访问第二个a,这时 left = map.get(‘a’) + 1 = 1,也就是left要后推的节奏,显然就不对了。所以这里要调整逻辑,保留最大值。

这样就可以了

left = Math.max(left,map.get(s.charAt(i)) + 1);

完整代码如下:

/**
     * 最长无重复字串
     * @param s
     * @return
     */
    public static int lengthOfLongestSubstring(String s) {
       if (s == null || s.length() == 0){
           return 0;
       }
        Map<Character,Integer> map = new HashMap<>();
       int max = 0;
       int left = 0;
       for(int right = 0; right < s.length(); right++){
           if (map.containsKey(s.charAt(right))){
               // 确保left一直向前走 不回头
               left = Math.max(left,map.get(s.charAt(right)) + 1);
           }
           map.put(s.charAt(right),right);
           max = Math.max(max,right - left + 1);
       }
       return max;

    }

除了上述方法,不用Hash存储索引,也是可以用滑动窗口的思想来解决的,感兴趣的是试一试。

至多包含两个不同字串的最长子串

根据上题目的变种,试想以下,这里如果求包含两个不同字串的最长子串呢。

你需要考虑两个问题:

  • 怎么判断是两个
  • 什么删除元素

如果还使用上述方法是否行的通,left的值应该怎么求舍?

要判断只有两个元素,当然还是Hash好用一些,每一个时刻,判断HashMap中不得超过3个元素。这里就要解决下一个问题,怎么移除了。

还是采用key-value的含义,这次将字符串里的字符都当做键,在窗口中的最右边的字符位置作为值。我们在看下伪代码是删除的逻辑:

del_index = Collections.min(hashMap.values());
left = del_index + 1;

还是看图更直接一些:
我们可以充分利用Map来解决这个问题:

 /**
     * 至少包含两个字符的最长子串
     * @param s
     * @return
     */
    public static int lengthOfLongestSubstringTwoDistinct(String s) {
        if (s.length() < 3){
            return s.length();
        }

        int left = 0;
        int right = 0;
        HashMap<Character,Integer> map = new HashMap<>();
        int maxLen = 2;
        while(left < s.length()){
            if (map.size() < 3){
                map.put(s.charAt(right),right++);
            }
            // 如果大于3就靠考虑了
            if (map.size() == 3){
                // 删除最左侧的位置
                int del_idx = Collections.min(map.values());
                map.remove(s.charAt(del_idx));
                // 更新窗口的位置
                left = del_idx + 1;
            }
            maxLen = Math.max(maxLen, right - left);
        }
        return maxLen;
    }

至多包含K个不同字串的最长子串

这更改2的位置就可以了,正如上个题目一样:

只要将判断HashMap大小为2改成k就可以了,超过2就是k+ 1,实现不就很简单的事情。

    /**
     * 至多包含k个字符的最长子串
     *
     * @param s
     * @param k
     * @return
     */
    public static int lengthOfLongestSubstringKDistinct(String s, int k) {
        if (s.length() < k + 1) {
            return s.length();
        }

        int left = 0, right = 0;
        HashMap<Character, Integer> hashmap = new HashMap<>();
        int maxLen = k;

        while (right < s.length()) {

            if (hashmap.size() < k + 1) {
                hashmap.put(s.charAt(right), right++);
            }
            // 如果大小达到了k个
            if (hashmap.size() == k + 1) {
                //
                int del_idx = Collections.min(hashmap.values());
                hashmap.remove(s.charAt(del_idx));
                // 窗口left的新位置
                left = del_idx + 1;
            }

            maxLen = Math.max(maxLen, right - left);
        }
        return maxLen;
    }

长度最小的子数组

参考题目介绍:209. 长度最小的子数组 - 力扣(LeetCode)

在这里插入图片描述
在这里插入图片描述
本题目可以使用双指针来解决,可以视为队列法。基本思路是先让元素不断入队,当入队元素和等于target时就记录以下此时队列的容量,如果队列元素之和大于target则开始出队,知道小于target则再入队。

当然如果等于target的情况,则记录以下此时队列的大小,之后继续先入队再出队。每当出现元素之和等于target时我们就保留容量最小的那个。

 /**
     * 长度最小的子数组
     * @param target 目标值
     * @param nums  数组
     * @return 返回大小
     */
    public static int minSubArrayLen(int target, int[] nums) {

        // 初始化变量
        int left = 0, right = 0,sum = 0,min = Integer.MAX_VALUE;
        while(right < nums.length){
            sum += nums[right++];
            while(sum >= target){
                min = Math.min(min,right - left);
                sum -= nums[left++];
            }
        }
        return min == Integer.MAX_VALUE ? 0 : min;
    }

盛水最多的容器

参考地址:11. 盛最多水的容器 - 力扣(LeetCode)
在这里插入图片描述
这个题目看似很复杂,但是换个思路很简单的,假设两个指针i,j。指向的水槽板高度分别为h[i],h[j],此时状态下水槽面积为s(i,j)。由于可容纳水的高度有两个板中的腐短板决定的,因此可以得出面积公式:

s(i,j) = min(h[i],h[j])*(j - i)

那么我们就要考虑以下问题:

每个状态,不论是长板还是短板向中间收缩一格,都会导致水槽边宽度-1变短:

  • 若向内移动短板,水槽得短板min(h[i],h[j])可能变大,因此下一个水槽得面积可能会增加
  • 若向内移动长板,水槽得短板min(h[i],h[j])不变或者变小,因此下一个水槽得面积一定是变小的

因此,这里初始化双指针分别位于水槽左右两端,循环每轮移动向内移动一格,并更新面积最大值,知道两个指针相遇时跳出;即可获得最大面积。

	/**
     * 盛最多水的容器
     * @param height the height of the array
     * @return
     */
    public static int maxArea(int[] height) {
        int i = 0, j = height.length,res = 0;
        while(i < j){
            // 那最小向中间移动
            res = height[i] < height[j] ?
                    Math.max(res,(j - i)* height[i++]):
                    Math.max(res,(j - i)* height[j++]);
        }
        return res;
    }

寻找字串异位词(排序)

如果两个字符串仅仅时字母出现的位置不一样,则称两者互相为对方的一个排列,也常称为异位词。

这里推荐两个题目⭐⭐⭐⭐:

567. 字符串的排列 - 力扣(LeetCode)

438. 找到字符串中所有字母异位词 - 力扣(LeetCode)

字符串的排序

参考题目地址:567. 字符串的排列 - 力扣(LeetCode)

在这里插入图片描述
本题最简单的方式就是用s1做窗口,依次比较得出结果,但是这样的效率太低,我们看看有没有更好更优的办法。

所谓异位词要抓住两点:

  • 字母类型一样
  • 出现个数相同

因此,我们可以创建一个为26的数组,每个位置对应a-z的个数,为了方便操作,索引做了调整,

index = s1.charAt(i) - 'a';

这个是字符串常用的技巧。

此时窗口一直向前移动就行了。

charArray[s2.charAt(right) - 'a']++

而left向右移动就执行

int left = right - sLen1;
charArray2[s2.charAt(left) - 'a']--;

完整代码是下面的:

  /**
     * 字符串的排列
     * @param s1
     * @param s2
     * @return
     */
    public static boolean checkInclusion(String s1, String s2) {
        // 参数检验
      int len1 = s1.length(),len2 = s2.length();
      if (len1 > len2){
          return false;
      }

        int[] chars1 = new int[26];
        int[] chars2 = new int[26];
        // 先读len1
        for (int i = 0; i < len1; i++){
            chars1[s1.charAt(i) - 'a']++;
            chars2[s1.charAt(i) - 'a']++;
        }

        if (Arrays.equals(chars1, chars2)){
            return true;
        }
        // 注意维护窗口变化
        for (int right = len1; right < len2; right++){
            chars2[s2.charAt(right) - 'a']++;
            int left = right - len1;
            chars2[s2.charAt(left) - 'a']--;
            if (Arrays.equals(chars1, chars2)){
                return true;
            }
        }
        return false;
    }

上面只是判断有没有,那么如果让你确定右几个呢?有或者如果存在的话,将异位词的开始位置和结束位置输出出来怎么样?那就看看下一题吧

找到字符串中所有字母异位

参考题目介绍:438. 找到字符串中所有字母异位词 - 力扣(LeetCode)

在这里插入图片描述
思路可以说是一摸一样,简单改下代码就可以了,唯一不同的是,采用List存放,出现异位词的,记录以下开始位置,那么可以直接将其加入到list中。

开下完整的代码:

    /**
     * 找到字符串中所有字母异位词
     * @param s1
     * @param s2
     * @return
     */
    public static List<Integer> findAnagrams(String s1, String s2) {
        // 参数检验
        int len1 = s1.length(),len2 = s2.length();
        if (len1 > len2){
            return new ArrayList<Integer>();
        }

        int[] chars1 = new int[26];
        int[] chars2 = new int[26];
        // 先读len1
        for (int i = 0; i < len1; i++){
            chars1[s1.charAt(i) - 'a']++;
            chars2[s1.charAt(i) - 'a']++;
        }
        ArrayList<Integer> res = new ArrayList<>();

        if (Arrays.equals(chars1, chars2)){
            res.add(0);
        }
        // 注意维护窗口变化
        for (int left = 0; left < len1 - len2; left++ ){
            chars2[s1.charAt(left) - 'a']--;
            int right = left + len1;
            chars2[s1.charAt(right) - 'a']++;
            if (Arrays.equals(chars1, chars2)){
            	// 拿到最最左位置
                res.add(left + 1);
            }
        }
        return res;
    }

总结

提示:滑动窗口问题;双指针问题;字符串变种;HashMap的特殊存储;模板套路


如果有帮助到你,请给题解点个赞和收藏,让更多的人看到 ~ ("▔□▔)/

如有不理解的地方,欢迎你在评论区给我留言,我都会逐一回复 ~

也欢迎你 关注我 ,喜欢交朋友,喜欢一起探讨问题。

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

36 机器学习(四):异常值检测|线性回归|逻辑回归|聚类算法|集成学习

文章目录 异常值检测箱线图z-score 保存模型 与 使用模型回归的性能评估线性回归正规方程的线性回归梯度下降的线性回归原理介绍L1 和 L2 正则化的介绍api介绍------LinearRegressionapi介绍------SGDRegressor 岭回归 和 Lasso 回归 逻辑回归基本使用原理介绍正向原理介绍损失…

Web前端接入Microsoft Azure AI文本翻译

Azure 文本翻译是 Azure AI 翻译服务的一项基于云的 REST API 功能。 文本翻译 API 支持实时快速准确地进行源到目标文本翻译。 文本翻译软件开发工具包 (SDK) 是一组库和工具&#xff0c;可用于轻松地将文本翻译 REST API 功能集成到应用程序中。 文本翻译 SDK 可跨 C#/.NET、…

大数据Flink(一百零一):SQL 表值函数(Table Function)

文章目录 SQL 表值函数(Table Function) SQL 表值函数(Table Function) Python UDTF,即 Python TableFunction,针对每一条输入数据,Python UDTF 可以产生 0 条、1 条或者多条输出数据,此外,一条输出数据可以包含多个列。比如以下示例,定义了一个名字为 split 的Pyt…

深度学习第四课

第九章 卷积神经网络解读 9.1 计算机视觉 目标分类 目标识别 64x64x312288 1000x1000x33000000 使用传统神经网络处理机器视觉面临的一个挑战是&#xff1a;数据的输入会非常大 一般的神经网络很难处理海量图像数据。解决这一问题的方法就是卷积神经网络 9.2 卷积运算 …

想要实现Email多账号管理,让SaleSmartly来帮你

Email营销是目前出海企业常见的模式&#xff0c;但是邮件信息整理起来确实不容易&#xff0c;选择一个实用的Email管理工具是很有必要的。SaleSmartly作为一个全渠道客户沟通平台&#xff0c;不仅可以聚合多个Email账号&#xff0c;不仅如此还可以聚合在线聊天&#xff08;Live…

自动驾驶的未来展望和挑战

自动驾驶技术是一项引人瞩目的创新&#xff0c;将在未来交通领域产生深远影响。然而&#xff0c;随着技术的不断演进&#xff0c;自动驾驶也面临着一系列挑战和障碍。本文将探讨自动驾驶的未来发展方向、技术面临的挑战&#xff0c;以及自动驾驶对社会和环境的潜在影响。 自动驾…

和鲸赞助丨第16届中国R会议暨2023 X-AGI大会通知

第16届中国 R 会议暨2023 X-AGI大会将于11月25-30日在中国人民大学召开&#xff0c;探讨数据科学和人工智能的相关进展&#xff0c;本次会议将采用线上会议和线下会议相结合的方式举办。 在过去的15年里&#xff0c;中国R会议一直致力于探讨数据科学在各学科、各行业的探索和实…

4大软件测试策略的特点和区别(单元测试、集成测试、确认测试和系统测试)

四大软件测试策略分别是单元测试、集成测试、确认测试和系统测试。 一、单元测试 单元测试也称为模块测试&#xff0c;它针对软件中的最小单元&#xff08;如函数、方法、类、模块等&#xff09;进行测试&#xff0c;以验证其是否符合预期的行为和结果。单元测试通常由开发人…

同为科技(TOWE)机架PDU产品在IDC数据中心机房建设中的应用

当今社会互联网发展迅速&#xff0c; 随着带宽需求的提升&#xff0c; 网络的保密性、安全性的要求就越来越迫切。PDU(Power Distribution Unit) 是 PDU具备电源分配和管理功能的电源分配管理器。PDU电源插座是多有设备运行的第一道也是最为密切的部件&#xff0c; PDU的好坏直…

linux,windows命令行输出控制指令,带颜色的信息,多行刷新,进度条效果,golang

一、带颜色的信息 linux 颜色及模式编号 // 前景 背景 颜色 // --------------------------------------- // 30 40 黑色 // 31 41 红色 // 32 42 绿色 // 33 43 黄色 // 34 44 蓝色 // 35 45 紫红色 // 36 46 青蓝色 // 37 47 白色 // // 模式代码 意义 //…

人大金仓三大兼容:MySQL迁移无忧

近日&#xff0c;MySQL 5.7停服事件引发广泛关注。MySQL目前已经成为中国用户使用非常广泛的数据库&#xff0c;其中5.7版本的用户比重又是最高的。随着信息技术应用创新深入各行各业&#xff0c;国产数据库对MySQL的平滑替换成为大势所趋。 作为数据库领域国家队&#xff0c;人…

记一次大型微服务项目本地打包迁移部署

记一次大型微服务项目本地打包迁移部署 引代码合并发布过程本地部署服务配置服务打包自启动测试外部依赖排除部分外部依赖 引 服务的运维也是一个挺复杂工作&#xff0c;如项目上线后的一次小版本发布&#xff0c;开发人员需要基于工程最新代码拉取feature分支&#xff0c;本地…

echarts-进度条

echarts-进度条 option {title: {text:"xxxx统计",left: 1%,top: 0%,textStyle: {color: "#2E3033",fontSize:18,},},tooltip: {axisPointer: {type: "shadow",},},grid: {top: 9%,left: "12%",right:"22%",bottom:"0…

基于springboot实现企业客户信息反馈平台管理系统项目【项目源码+论文说明】

基于springboot实现企业客户信息反馈平台管理系统演示 摘要 网络的广泛应用给生活带来了十分的便利。所以把企业客户信息反馈管理与现在网络相结合&#xff0c;利用java技术建设企业客户信息反馈平台&#xff0c;实现企业客户信息反馈的信息化。则对于进一步提高企业客户信息反…

6.6 Elasticsearch(六)京淘项目改造

文章目录 1.项目准备2.基础配置2.1 添加pom.xml依赖2.2 yml配置es服务器地址列表 3.具体实现3.1 item实体类封装3.2 添加接口3.3 SearchController 4.search.jsp界面4.1 搜索内容展示4.2 高亮内容样式设置4.3 搜索框内容回填4.4 添加上下页按钮 1.项目准备 我们切换回到此前的…

基于ElasticSearch+Vue实现简易搜索

基于ElasticSearchVue实现简易搜索 一、模拟数据 产品名称描述价格库存数量品牌名称智能手表智能手表&#xff0c;具有健康跟踪和通知功能。199.991000TechWatch4K智能电视4K分辨率智能电视&#xff0c;提供出色的画质。699.99500VisionTech无线耳机降噪无线耳机&#xff0c;…

ChineseChess2

中国象棋&#xff1a;黑将&#xff0c;红帅双炮&#xff0c;只要红帅中间露头怎么走怎么赢 卡主黑将的走位&#xff0c;控制住就好了 ChineseChess-CSDN博客

17 结构型模式-享元模式

1 享元模式介绍 2 享元模式原理 3 享元模式实现 抽象享元类可以是一个接口也可以是一个抽象类,作为所有享元类的公共父类, 主要作用是提高系统的可扩展性. //* 抽象享元类 public abstract class Flyweight {public abstract void operation(String extrinsicState); }具体享…

2023了,是时候使用pnpm了!

2023了&#xff0c;是时候使用pnpm了&#xff01; Excerpt 2023了&#xff0c;是时候使用pnpm了&#xff01; 什么是pnpm pnpm代表performant npm&#xff08;高性能的npm&#xff09;&#xff0c;同npm和Yarn&#xff0c;都属于Javascript包管理安装工具&#xff0c;它较npm和…

智慧垃圾站:AI视频智能识别技术助力智慧环保项目,以“智”替人强监管

一、背景分析 建设“技术先进、架构合理、开放智能、安全可靠”的智慧环保平台&#xff0c;整合环境相关的数据&#xff0c;对接已建业务系统&#xff0c;将环境相关数据进行统一管理&#xff0c;结合GIS技术进行监测、监控信息的展现和挖掘分析&#xff0c;实现业务数据的快速…