滑动窗口篇: 长度最小子数组|无重复字符最长字串

目录

1、滑动窗口算法

1.1 核心概念

1.2 基本步骤

1.3 应用场景

1.4 优势

2. leetcode 209 长度最小子数组

暴力解题思路:

滑动窗口思路:

3、无重复字符的最长子串

暴力解题思路: 

滑动窗口思路:


1、滑动窗口算法

        滑动窗口算法是一种在处理数组、字符串或其他序列数据结构时非常有用的技巧,特别适用于寻找特定条件的连续子序列问题。这种算法通过维护一个可变的“窗口”,在序列上滑动来高效地解决问题。

        使用滑动窗口通常会用到双指针,我们在前面也讲解过双指针,可以参考双指针算法篇:两数之和 、三数之和

1.1 核心概念

窗口:想象一个可以在序列上左右移动的窗口,窗口内的元素是我们关注的对象。

双指针通常使用两个指针(左指针和右指针)来表示窗口的边界。左指针代表窗口的起始位置,右指针代表窗口的结束位置。

动态调整:根据问题需求,窗口的大小可能固定也可能变化,通过移动左右指针来调整窗口覆盖的范围。

1.2 基本步骤

  1. 初始化:设置左指针 left 和右指针 right 初始位置,通常从序列的起始位置开始。根据问题可能还需要初始化一些状态变量,比如窗口内元素的和、计数器等。
  2. 扩展窗口:逐步将右指针向右移动,每次移动都检查新增元素是否满足问题条件,同时更新窗口内的相关信息(如总和、最大值、最小值等)。
  3. 收缩窗口:当窗口满足某个终止条件时(如总和达到了目标值、找到了所需子序列等),开始考虑缩小窗口。通过移动左指针来排除窗口左侧的元素,同时保持窗口内信息的更新。
  4. 重复步骤2和3:在序列未完全探索之前,持续执行扩展和收缩窗口的操作。
  5. 结果处理:根据问题需求,在算法执行过程中或结束后处理并返回最终结果。

1.3 应用场景

最大/最小子数组和:找到数组中和最大的(或最小的)连续子数组。

无重复字符的最长子串:在字符串中寻找不含重复字符的最长子串。

子数组问题:如找到和大于等于特定值的最小子数组长度。

字符串匹配:如寻找包含所有字母的最小子串等。

1.4 优势

效率:将原本可能需要嵌套循环的问题简化为单层循环,显著提高了算法的时间复杂度,通常达到O(n)级别。

灵活性:适用于多种类型的问题,只需适当调整窗口的扩展和收缩条件。

2. leetcode 209 长度最小子数组

题目描述

给定一个含有 n 个正整数的数组和一个正整数 target 。

找出该数组中满足其总和大于等于 target 的长度最小的 连续子数组

 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度如果不存在符合条件的子数组,返回 0 。

示例:

示例 1:

输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。

示例 2:

输入:target = 4, nums = [1,4,4]
输出:1

示例 3:

输入:target = 11, nums = [1,1,1,1,1,1,1,1]
输出:0

暴力解题思路:

  1. 双重循环:外层循环遍历数组的每个起始位置,内层循环尝试以该位置为起点的所有可能的子数组。
  2. 计算子数组和:对每个子数组,累加其元素和。
  3. 判断与更新:如果当前子数组的和达到或超过目标值,就比较并更新最小长度。
  4. 结果处理:如果最小长度仍为初始值(即大于数组长度),说明没有找到符合条件的子数组,返回0;否则返回找到的最小长度。

代码示例:

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
         int n = nums.size();
    if (n == 0) return 0;
    
    // 初始化结果为不可能的情况,即比数组长度还大1
    int minLength = n + 1;
    
    // 双重循环,枚举所有子数组
    for (int start = 0; start < n; ++start) {
        int sum = 0;
        for (int end = start; end < n; ++end) {
            sum += nums[end]; // 计算当前子数组的和
            // 如果当前子数组和大于等于目标值,更新最短长度
            if (sum >= target) {
                minLength = min(minLength, end - start + 1);
                break; // 找到一个满足条件的子数组后,提前结束内层循环
            }
        }
    }
    
    // 如果没有找到满足条件的子数组,返回0;否则返回找到的最短长度
    return minLength == n + 1 ? 0 : minLength;

    }
};

时间复杂度较高,为O(n^2),在数据规模较大时效率低下,不适用于性能敏感的场景。相比滑动窗口算法的线性时间复杂度O(n),暴力解法在效率上明显不足。

暴力解法虽然思路简单,但是效率低下,在这个题目要求下会超时,暴力是将所有子数组列举一遍,但是比如start到end间的数据已经计算过一次了,再计算就没有必要了,所以只需要在sum中减去nums[left],left++即可。

滑动窗口思路:

  1. 初始化:

    • 初始化两个指针 left 和 right,均从0开始。
    • 初始化 sum(当前窗口内元素的和)为0,ret(记录满足条件的最短子数组长度,默认极大值INT_MAX)。
  2. 双指针遍历:

    • 使用 right 指针遍历数组,将新元素加入窗口和 sum 中。(进窗口)
  3. 窗口内求解:(循环)

    • 当窗口内元素和 sum 大于等于目标值 target 时:
      • 更新 ret 的值为当前子数组长度(right-left+1)和原 ret 中的较小值。
      • 然后从窗口(即从 sum 中)移除左侧元素(nums[left]),并将 left 指针右移一位,继续寻找可能更小的满足条件的子数组。(出窗口)
  4. 循环结束处理:

    • 遍历结束后,若 ret 仍为初始值 INT_MAX,说明没有找到符合条件的子数组,返回0;否则返回 ret

代码示例: 

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int sum=0;
        int ret=INT_MAX;
        for(int left=0,right=0;right<nums.size();right++)
        {
            sum+=nums[right];
            while(sum>=target)
            {
                ret=min(ret,right-left+1);
                sum-=nums[left];
                left++;
            }
        }
        return ret==INT_MAX?0:ret;

    }
};

滑动窗口简化了循环,时间效率上提示了很多,不会超时。

3、无重复字符的最长子串

题目描述

给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。

 示例:

示例 1:

输入: s = "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

示例 2:

输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。

示例 3:

输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。 请注意,你的答案必须是 子串 的长度,"pwke"是一个子序列,不是子串。

暴力解题思路: 

  1. 双重循环:外层循环遍历字符串的每个字符作为子串的起始点,内层循环尝试以该字符为起点的后续所有字符作为子串的一部分。
  2. 无重复检查:使用unordered_set来存储当前子串中的字符,以检查是否有重复。一旦发现重复字符,立即停止对该起始位置的进一步扩展,并检查下一个起始位置。
  3. 更新最大长度:在每次内循环结束时,用当前无重复子串的长度更新最长无重复子串的长度。

代码示例:

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int n = s.length();
    int maxLength = 0; // 用于记录最长无重复子串的长度

    // 枚举所有可能的子串起始位置
    for (int i = 0; i < n; ++i) {
        unordered_set<char> charSet; // 用于检查当前子串是否有重复字符
        int uniqueLength = 0; // 记录当前子串的长度

        // 枚举以i为起始的子串的结束位置j
        for (int j = i; j < n; ++j) {
            // 如果当前字符不在charSet中,说明是新的唯一字符
            if (charSet.find(s[j]) == charSet.end()) {
                charSet.insert(s[j]);
                uniqueLength++; // 子串长度增加
            } else {
                // 如果当前字符已经出现过,则停止当前子串的检查
                break;
            }
        }

        // 更新最大无重复子串长度
        maxLength = max(maxLength, uniqueLength);
    }

    return maxLength;

    }
};

暴力解法虽然勉强跑过了,但还是效率比较低。

滑动窗口思路:

  1. 初始化:

    • 定义变量 ret 用来记录最长子串长度,初始化为0。
    • 获取字符串长度 n
    • 初始化一个大小为128的整型数组 hash 用于记录ASCII表中每个字符出现的次数(假设字符串只包含ASCII字符)。
  2. 双指针遍历:

    • 使用 left 和 right 分别作为窗口的左右边界,初始时都指向字符串的起始位置。
    • right 向右移动,遍历整个字符串。
  3. 字符计数与窗口调整:

    • 每遇到一个字符,就在 hash 表中对应字符的计数加一。(进窗口)hash[s[right]]++;
    • 当 hash[s[right]] 大于1(循环),说明当前字符在窗口内有重复,这时需要移动 left 指针来缩小窗口,直到窗口内没有重复字符。同时,hash[s[left]] - -,并将 left 向右移动。(出窗口)
  4. 更新最长子串长度:

    在每次窗口调整后(即窗口内无重复字符时),用 right-left+1 计算当前无重复子串的长度,并用 max() 函数更新全局最长子串长度 ret
  5. 返回结果:

    遍历结束后,返回最长无重复字符子串的长度 ret

代码示例:

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int ret=0;
        int n=s.size();
        int hash[128]{0};
        for(int left=0,right=0;right<n;right++)
        {
            hash[s[right]]++;
            while(hash[s[right]]>1)
            {
                hash[s[left]]--;
                left++;
            }
            ret=max(ret,right-left+1);
        }
        return ret;
    }
};

滑动窗口简化了循环,通过巧妙地控制窗口的扩张与收缩来高效地找到满足条件的最长子串,相较于暴力解法,其时间复杂度大大降低至O(n)。

两者可能相似,取决于具体实现。暴力解法可能会使用额外的数据结构(如哈希表)来辅助检查,滑动窗口同样可能使用哈希表来跟踪窗口内的元素,但整体上,滑动窗口的空间复杂度一般更为可控且高效,因为它不需要存储所有可能的子序列。

暴力解法:通常具有较高的时间复杂度,例如在解决“寻找字符串中无重复字符的最长子串”问题时,暴力解法的时间复杂度为O(n^2),因为它需要对每个子串进行检查,而子串数量是n(n+1)/2。

暴力解法在数据规模较小时可能尚可接受,但在面对大规模数据集时,其效率低下,难以在有限时间内得到结果。

滑动窗口:时间复杂度通常为O(n),这是因为滑动窗口算法只需要遍历一次输入序列。通过动态调整窗口的大小,它能够高效地找到满足条件的子序列,避免了冗余的检查。

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

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

相关文章

React 第二十九章 React 和 Vue 描述页面的区别

面试题&#xff1a;React 和 Vue 是如何描述 UI 界面的&#xff1f;有一些什么样的区别&#xff1f; 标准且浅显的回答&#xff1a; React 中使用的是 JSX&#xff0c;Vue 中使用的是模板来描述界面 前端领域经过长期的发展&#xff0c;目前有两种主流的描述 UI 的方案&#xf…

基于MWORKS 2024a的MIMO-OFDM 无线通信系统设计

一、引言 在终端设备和数据流量爆发式增长的今天&#xff0c;如何提升通信系统容量、能量效率和频谱利用率成为5G通信的关键问题之一。大规模天线阵列作为5G及B5G无线通信关键技术通过把原有发送端天线数量提升一个或多个数量级&#xff0c;实现波束聚集、控制波束转向&#x…

《架构风清扬-Java面试系列第29讲》聊聊DelayQueue的使用场景

DelayQueue是BlockingQueue接口的一个实现类之一 这个属于基础性问题&#xff0c;老规矩&#xff0c;我们将从使用场景和代码示例来进行讲解 来&#xff0c;思考片刻&#xff0c;给出你的答案 1&#xff0c;使用场景 实现&#xff1a;延迟队列&#xff0c;其中元素只有在其预定…

使用Flask-Admin创建强大的后台管理系统

文章目录 安装Flask-Admin创建Flask应用添加Flask-Admin添加模型扩展延伸自定义视图权限管理文件上传 结语 在Web应用开发中&#xff0c;后台管理系统是至关重要的组成部分&#xff0c;它能够让管理员轻松管理应用的各种数据和配置。Flask-Admin是一个功能强大的Flask扩展&…

常见排序算法——希尔排序

基本原理 希尔排序在插入排序的基础之上&#xff0c;将待排序序列分成组&#xff0c;分成 gap 个组&#xff0c;组的数量通过 length / 2 获得&#xff0c;比如6个元素的序列&#xff0c;那么就是 3 个组&#xff0c;每个组两个元素&#xff0c;然后将每个组的元素进行插入排…

【Web后端】servlet基本概念

1.ServletAPI架构 HttpServlet继承GenericServletGenericServlet实现了Servlet接口&#xff0c;ServletConfig接口,Serializable接口自定义Servlet继承HttpServlet 2.Servlet生命周期 第一步&#xff1a;容器加载Servlet第二步&#xff1a;调用Servlet的无参构造方法&#xf…

【程序设计和c语言-谭浩强配套】(适合专升本、考研)

一晃大半年没更新了&#xff0c;这一年一直在备考&#xff0c;想着这几天把前段时间学的c语言给大家分享一下&#xff0c;在此做了一个专栏&#xff0c;有需要的小伙伴可私信获取o。 简介&#xff1a;本专栏所有内容皆适合专升本、考研的复习资料&#xff0c;本人手上也有日常…

关于架构设计:什么是完美?

这篇不谈技术。 为什么写这篇文章&#xff1f;因为刚毕业时看一本关于软件架构设计的书&#xff0c;记得有一句关于完美的话&#xff0c;但后来无论如何都想不起来了。只记得和飞机有关。而今年在看“The Pragmatic Programmer: your journey to mastery”第2版&#xff08;20…

##13 如何在Python中优雅地使用异常处理

文章目录 引言1. 异常处理基础2. 处理多种异常3. 捕捉所有异常4. finally 语句5. 自定义异常结语参考链接 引言 在编程中&#xff0c;错误是在所难免的。Python提供了异常处理机制&#xff0c;允许程序在遇到错误时优雅地恢复。本文将介绍Python中异常处理的基本概念&#xff…

Mac YOLO V9推理测试(基于ultralytics)

环境&#xff1a; Mac M1 (MacOS Sonoma 14.3.1) Python 3.11PyTorch 2.1.2 一、准备工作 使用YOLO一般都会接触ultralytics这个框架&#xff0c;今天来试试用该框架进行YOLO V9模型的推理。 YOLOv9目前提供了四种模型下载&#xff1a;yolov9-c.pt、yolov9-e.pt、gelan-c.p…

异常处理/__LINE__ 与 __FILE__ 宏在调试和异常处理中的高级使用

文章目录 概述痛点分析_LINE_ 代码所在行号_LINE_ 直接转为字符串_LINE_ 作为整型数据使用_LINE_标记宏函数的调用位置 _FILE_ 代码所在文件名简单实验不期望 _FILE_ 宏代表全路径 assert 使用了 _FILE_ 和 _LINE_借助TLS技术小结 概述 _LINE_和_FILE_是C/C中的预定义宏&#…

【Sql-02】 求每个省份最新登陆的三条数据

SQL 输出要求数据准备sql查询结果 输出要求 要求输出&#xff0c;userid_1,logtime_1,userid_2,logtime_2,userid_3,logtime_3 数据准备 CREATE TABLE sqltest (province varchar(32) NOT NULL,userid varchar(250) DEFAULT NULL,logtime datetime ) ENGINEInnoDB DEFAULT C…

Spring框架中常见注解

Spring&#xff1a; SpringMVC&#xff1a; RequestMapping用在类上表示所有该类下方法的父路径 RequestParam 做映射&#xff0c;前端请求的参数映射到控制器Controller的处理方法上的参数上。 【当参数需要设置默认值&#xff08;前端没有发送这个参数&#xff09;、参数名…

禁止打开浏览器时弹出 internet explorer 11 停用的通知

计算机管理&#xff08;我的电脑图标上右键&#xff09; - 管理模板 - windows 组件 - internet explorer 启用隐藏 internet explorer 11 停用通知&#xff0c;如下图所示

每日算法之二叉树的最近公共祖先

题目描述 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#xff1a;“对于有根树 T 的两个节点 p、q&#xff0c;最近公共祖先表示为一个节点 x&#xff0c;满足 x 是 p、q 的祖先且 x 的深度尽可能大&#xff08;一个节点也可以是…

【Python特征工程系列】排列重要性法分析特征重要性-随机森林模型为例(案例+源码)

这是我的第277篇原创文章。 一、引言 排列重要性&#xff08;Permutation Importance&#xff09;是一种基于模型的方法&#xff0c;用于评估每个特征对模型性能的影响程度。该方法通过随机打乱单个特征的值并观察模型性能的变化&#xff0c;从而确定特征的重要性。如果某个特征…

模型预测控制与模糊控制 —— 潜力控制方案探讨

一、需要多少先验信息&#xff1f; 此图片来源于网络&#xff0c;所有的控制与估计过程都涉及了先验信息与后验信息之间的博弈 评估一个控制方案对先验信息的需求量大小和先验信息质量对其影响的方法涉及以下几个方面&#xff1a; 1、控制方案的理论分析&#xff1a; 详细分析…

【UE Niagara】在UI上生成粒子

效果 步骤 1. 在虚幻商城中将“Niagara UI Render”插件安装到引擎 2. 打开虚幻编辑器&#xff0c;勾选插件“Niagara UI Renderer”&#xff0c;然后重启编辑器 3. 先创建一个控件蓝图&#xff0c;该控件蓝图只包含一个按钮 这里设置尺寸框尺寸为200*50 4. 显示该控件 5. 新…

MFC中关于CMutex类的学习

MFC中关于CMutex类的学习 最近在项目中要实现两个线程之间的同步&#xff0c;MFC中提供了4个类&#xff0c;分别是CMutex(互斥量)、CCriticalSection(临界区)、CEvent(事件对象)、CSemaphore(信号量)。有关这4个类的说明&#xff0c;大家可以参考微软官方文档&#xff1a; CM…

4. 从感知机到神经网络

目录 1. 从感知机到神经网络 2. 最简单的神经网络 3. 激活函数的引入 1. 从感知机到神经网络 之前章节我们了解了感知机&#xff0c;感知机可以处理与门、非与门、或门、异或门等逻辑运算&#xff1b;不过在感知机中设定权重的工作是由人工来做的&#xff0c;而设定合适的&a…