leetcode滑动窗口问题总结 Python

目录

一、理论

二、例题

1. 最长无重复字符串

2. 长度最小的子数组

3. 字符串的排列

4. 最小覆盖子串

5. 滑动窗口最大值


一、理论

滑动窗口是一类比较重要的解题思路,一般来说我们面对的都是非定长窗口,所以一般需要定义两个指针 left 和 right,分别用来限制窗口的左边界和右边界。在解题时一般需要设定两个嵌套的循环,外循环不设定条件,完全是遍历模式,驱动右指针的移动;内循环需要设定条件,在满足条件的情况下,驱动左指针的移动。整体实现滑动窗口的向右移动。外循环虽然没有设定条件,但其实存在隐藏的条件就是不满足内循环所设定的条件时运行。在这个框架下,我们可以增加对窗口长度和数据的记录,进而实现丰富的功能。

def fun():left = 0     # 定义左指针right = 0    # 定义右指针length = 0   # 记录窗口长度# 可以定义数据结构记录窗口元素,例如set(), set().add(), set.remove()while right < len(s):     # 首先移动右指针length += 1           # 每移动一次右指针,窗口长度加一# 可以做一下数据操作,例如set().add()while check():        # 满足某个条件下移动左指针# 可以做一下数据操作,例如set().remove()left += 1         # 然后移动左指针length -= 1       # 每一定一次左指针,窗口长度减一right += 1            # 真正移动右指针return 

二、例题

1. 最长无重复字符串

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

示例:

输入: s = "abcabcbb"

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

该问题可以使用动态规划的思想来解,具体可以看leetcode动态规划问题总结 Python,本题还可以使用变长滑动窗口的思路来解。定义两个指针 left 和 right,以及一个集合 lookup,lookup 表示以右指针 right 为结尾的最长无重复字符串集合,实时更新 left、right 和 lookup。然后向右移动右指针(隐藏条件是 s[right] 不存在于 lookup),直至 s[right] 存在于 lookup,然后固定右指针,右移左指针,直至 s[right] 不存在于 lookup。该双指针移动的结果是完备的,当移动右指针时,可以保证同步左移左指针一定会造成出现重复字符,若同步右移左指针则会造成无重复字符串变短,并小于已找到的无重复字符串,所以右指针右移过程中并没有错过任何有用的无重复字符串。当右指针固定,右移左指针时也是完备的,此时如果左移则不会生成新的无重复字符串,所以没必要,但如果 s[right] 不存在于 lookup 时,还继续右移左指针,会造成无重复字符串变短,并小于已找到的无重复字符串,所以也没有必要继续右移。思路确定好了以后,就可以通过滑动窗口的两个循环嵌套实现编程,外层循环控制右指针递增右移,不用设定判断条件(其实存在隐含判断条件,就是与内层判断条件相反),内层循环需要设定判断条件,内层循环还需要控制左指针的右移。

def lengthOfLongestSubstring(s):if not s: return 0left = 0lookup = set()n = len(s)max_len = 0cur_len = 0for i in range(n):cur_len += 1while s[i] in lookup:lookup.remove(s[left])left += 1cur_len -= 1if cur_len > max_len: max_len = cur_lenlookup.add(s[i])return max_len

2. 长度最小的子数组

给定一个含有 n 个正整数的数组和一个正整数 target 。找出该数组中满足其总和大于等于 target 的长度最小的连续子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。

示例:

输入:target = 7, nums = [2,3,1,2,4,3]

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

该问题属于变长滑动窗口问题,定义两个指针 start 和 end。在子串求和小于 target 时,end 向右移动,直至子串求和大于等于 target,然后固定 end,右移 start,直至子串求和小于 target,然后再次移动 start,循环往复。该种移动方式的解是完备的,只是缩小了解的搜索范围,所以时间复杂度更优。在右移 end 时,子串求和小于 target,如果左移 start,虽然有可能得到求和大于 target 的子串,但是这些子串的长度一定大于已找到的子串,所以并不属于所求解的范围,亦或者根本无法左移 start(=0),此时如果右移 start,那所有子串的和都小于 target,所以也不属于求解的范围。在 end 固定时,如果左移 start,依然不属于求解范围,因为窗口长度会增加,超过了现有解的长度,只能右移 start,右移至子串求和小于 target 后就不能再右移了,因为继续右移不可能找到子串求和大于 target 的解。编程思路是两层循环嵌套,外层循环控制 end 的移动,隐藏条件是子串求和小于 target,内层循环控制 start 的移动,判断条件是子串求和大于等于 target。

def minSubArrayLen(s, nums):if not nums:return 0n = len(nums)ans = n + 1start, end = 0, 0total = 0while end < n:total += nums[end]while total >= s:ans = min(ans, end - start + 1)total -= nums[start]start += 1end += 1return 0 if ans == n + 1 else ans

3. 字符串的排列

给你两个字符串 s1 和 s2 ,写一个函数来判断 s2 是否包含 s1 的排列。如果是,返回 true ;否则,返回 false 。换句话说,s1 的排列之一是 s2 的子串 。

示例:

输入:s1 = "ab" s2 = "eidbaooo"

输出:true    解释:s2 包含 s1 的排列之一 ("ba").

该问题属于定长滑动窗口问题,以一个固定长度 len(s1) 进行滑动,所以该问题的核心问题在于,怎么判断一个长度为 len(s1) 的子集是否为 s1 的排列,这里采用的方式是通过字典统计每个字符出现的频率,然后比较两个字典是否相等,如果相等,则两个字符串互为排列。当窗口滑动时,只需要将新加入的字符添加到字典中,同步将剔除的字符从字典中剔除即可。

def checkInclusion(s1, s2):l1 = len(s1)l2 = len(s2)if l2 < l1:                  # 若字符串s2的长度小于s1,则返回falsereturn Falses = 'abcdefghijklmnopqrstuvwxyz'dict1 = {}dict2 = {}for char in s:dict1[char] = 0          # 初始化字典,key为字母,value为字母出现的次数,都初始化为0dict2[char] = 0for i in range(l1):dict1[s1[i]] += 1        # 首先计算前l1长度的不同字母出现次数dict2[s2[i]] += 1if dict1 == dict2:           # 若两个字典相等,则说明字符串的[0:l1]是字符串l1的不同排列return Truefor i in range(l2-l1):       # 开始往后查找,每次移动一个位置dict2[s2[i]] -= 1        # 减去滑动比较得前一个字母出现的次数dict2[s2[i+l1]] += 1     # 加上滑动后加进来的字母出现的次数if dict1 == dict2:       # 若相等,则返回truereturn Truereturn False

4. 最小覆盖子串

给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 " " 。注意:对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。如果 s 中存在这样的子串,我们保证它是唯一的答案。

示例:

输入:s = "ADOBECODEBANC", t = "ABC"

输出:"BANC"     解释:最小覆盖子串 "BANC" 包含来自字符串 t 的 'A'、'B' 和 'C'。

该问题采用滑动窗口的思路来解,定义左右两个指针,均起于 0 位置,右指针在滑动窗口不包含 t 字符串的情况下向右移动,当右指针使得滑动窗口包含 t 时,固定右指针,右移左指针,直至滑动窗口不包含 t 字符串,然后固定左指针,再次右移右指针,循环往复,直至右指针达到最右侧。该逻辑是完备的,当右指针右移时,起初如果不左移左指针,那滑动窗口并不包含 t 字符串,所以没必要检查每个路过的字符以及每个字符对应的所有可能字符串,如果左移左指针,会造成字符串长度增加,也不可能被使用,所以右指针右移时并没有错过满足条件的解。当滑动窗口包含 t 字符串时,左指针右移,也是完备的,左指针没必要左移,因为左移会增加子集的长度,而我们已经有更短的满足条件的解,所以左指针只能右移,当右移至滑动窗口不包含 t 字符串时,停止右移,因为继续右移只会得到一堆不满足条件的子集。所以实际右指针针对大部分字符仅仅只是检查了一次,针对剩余的小批量字符也只是检查了很少一部分对应字符串,整体算下来,左右指针的时间复杂度都是O(N)级别,如果是暴力法,那仅仅获取所有子集的时间复杂度就是O(N^2)。具体编程思路采用嵌套两个循环的方式进行,外循环对应右指针,内循环对应左指针,外循环直接无条件遍历,内循环基于判断条件遍历,其实外循环存在隐藏条件,条件为内循环判断条件的对立面。最后就剩余一个问题,如何判断一个字符串是否存在于另一个字符串中,这里采用的是字典统计法,通过字典统一字符串中每个字符的出现频率,通过比较两个字典进行判断。

def minWindow(s, t):def check(dict1, dict2):                    # 定义一个函数检查dict2是否为dict1的子集for key, value in dict2.items():if dict1[key] < value:return Falsereturn Trueif len(s) < len(t): return ""key = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJGKLMNOPQRSTUVWXYZ"  # 初始化两个字典,分别记录t和s前部分的字符分布dict1 = {}                                  # 如果使用collections.Counter()效率关于更高dict2 = {}for i in range(len(key)):dict1[key[i]] = 0dict2[key[i]] = 0for i in range(len(t)):dict1[s[i]] += 1                        # 得到s前len(t)的字符分布dict2[t[i]] += 1                        # 得到t的字符分布if check(dict1, dict2): return s[0:len(t)]  # 判断右指针为len(t)-1的起始情况min_len = len(s) + 1min_beign = 0min_end = 0left = 0                                    # 滑动窗口的左指针for right in range(len(t), len(s)):         # 滑动窗口的右指针,从len(t)开始,隐藏条件:在dict2不是dict1子集的情况下移动右指针dict1[s[right]] += 1                    # 每移动一个右指针必须马上修改其指纹dict1,对dict1的修改必须正在check函数之前while check(dict1, dict2):              # 条件:在dict2是dict1子集的情况下移动左指针if min_len > right - left + 1:      # 此时得到满足条件的子集min_len = right - left + 1min_beign = leftmin_end = rightdict1[s[left]] -= 1left += 1if min_end == 0:return ""else:return s[min_beign:min_end+1]

5. 滑动窗口最大值

给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。返回滑动窗口中的最大值 。

示例:

输入:nums = [1,3,-1,-3,5,3,6,7], k = 3

输出:[3,3,5,5,6,7]

该问题最朴素的想法就是固定窗口长度,向右滑动,直接找每个窗口的最大值即可,但是这样编程的时间复杂度不满足要求。本题需要借助大根堆这个完全二叉树结构(在 python 中 heapq 包对应的是小根堆,所以需要加入负号变大根堆)实现最大值的寻找,具体操作为起初用最左侧的的 k 个数值搭建一颗大根堆的完全二叉树,然后向右滑动窗口,每滑动一次,向大根堆增加一个数值,但此时的问题是如何删除滑动窗口最左侧剔除的数值,或者如何保证大根堆的最大值是在滑动窗口的当前范围内呢?其实这里不需要删除滑动窗口最左侧的值,只要保证大根堆的最大值永远落在滑动窗口内即可,具体操作就是使用 while 循环,在大根堆的最大值的索引不在滑动窗口范围内的情况下,持续将大根堆的主根剔除,在 while 循环终止后,大根堆的最大值一定落在滑动窗口的当前范围内,此时大根堆的最大值就是当前滑动窗口的最大值。此题的关键是想清楚,当滑动窗口右移时,没必要把滑动窗口左侧剔除的数据从大根堆中剔除。

import heapqdef maxSlidingWindow(nums, k):n = len(nums)# 注意 Python 默认的优先队列是小根堆q = [(-nums[i], i) for i in range(k)]heapq.heapify(q)ans = [-q[0][0]]for i in range(k, n):heapq.heappush(q, (-nums[i], i))  # 把新的字符放入大根堆while q[0][1] <= i - k:           # 没必要把所有窗口外的数据踢走,只需要让找到的最大值在窗口范围内即可heapq.heappop(q)ans.append(-q[0][0])return ans

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

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

相关文章

Unity报错:[SteamVR] Not Initialized (109)的解决方法

问题描述 使用HTC vive 头像进行SteamVR插件的示例场景进行测试&#xff0c;发现头显场景无法跳转到运行场景&#xff08;Unity 项目可以运行&#xff0c;仅出现警告&#xff09;。 具体如下&#xff1a; [SteamVR] Not Initialized (109) [SteamVR] Initialization failed…

面试宝典之消息中间件面试题

RabbitMq: 1、RabbitMQ有啥用处&#xff1f; &#xff08;1&#xff09;服务间异步通信 &#xff08;2&#xff09;顺序消费 &#xff08;3&#xff09;定时任务 &#xff08;4&#xff09;请求削峰 2、RabbitMQ有哪些常用的工作模式&#xff1f; 工作模式&#xff08;Work…

常用接口抓包以及接口测试工具总结

接口 统称为API&#xff0c;程序与程序之间的对接、交接。 接口测试主要用于检测外部系统与系统之间以及内部各个子系统之间的交互点&#xff0c;主要是为了检验不同组件&#xff08;模块&#xff09;之间数据的传递是否正确&#xff0c;同时接口测试还要测试当前系统与第三方…

OpenHarmony自定义Launcher

前言 OpenHarmony源码版本:4.0release 开发板:DAYU / rk3568 DevEco Studio版本:4.0.0.600 自定义效果: 一、Launcher源码下载 Launcher源码地址:https://gitee.com/openharmony/applications_launcher 切换分支为OpenHarmony-4.0-Release,并下载源码 二、Launcher源…

2024.1.9 基于 Jedis 通过 Java 客户端连接 Redis 服务器

目录 引言 RESP 协议 Redis 通信过程 实现步骤 步骤一 步骤二 步骤三 步骤四 引言 在 Redis 命令行客户端中手敲命令并不是我们日常开发中的主要形式而更多的时候是使用 Redis 的 API 来实现定制化的 Redis 客户端程序&#xff0c;进而操作 Redis 服务器即使用程序来操…

mysql生成到当前时间的时间序列,报表按时间补0

生成本月每日的时间序列 SELECT DATE_FORMAT(date_add( CONCAT(YEAR(Date(curdate())),‘-0’,MONTH(Date(curdate())),‘-’,‘01’), INTERVAL ( cast( help_topic_id AS signed) ) DAY ) ,‘%Y-%m-%d’ ) FROM mysql.help_topic WHERE help_topic_id < DAY ( curdate( ) …

利用Type类来获得字段名称(Unity C#中的反射)

使用Type类以前需要引用反射的命名空间&#xff1a; using System.Reflection; 以下是完整代码&#xff1a; public class ReflectionDemo : MonoBehaviour {void Start(){A a new A();B b new B();A[] abArraynew A[] { a, b };foreach(A v in abArray){Type t v.GetTyp…

SaaS先驱Salesforce发展史

Salesforce是云计算和SaaS领域的先驱&#xff0c;大致经过5个不同发展阶段 第一个阶段&#xff1a;SaaS CRM发展初期 Salesforce成立时间是1999年&#xff0c;其SaaS业务的Idea的灵感起源于IaaS巨头亚马逊。初期标榜的竞品Siebel早期投入高、很难上手、功能过于复杂、实用性不强…

使用Excel批量给数据添加单引号和逗号

表格制作过程如下&#xff1a; A2表格暂时为空&#xff0c;模板建立完成以后&#xff0c;用来放置原始数据&#xff1b; 在B2表格内输入公式&#xff1a; ""&A2&""&"," 敲击回车&#xff1b; 解释&#xff1a; B2表格的公式&q…

【React系列】ES6学习笔记(一)let与const、解构赋值、函数参数默认值\rest参数\箭头函数、数组和对象的扩展、Set和Map等

本文参考自电子书《ECMAScript 6 入门》&#xff1a;https://es6.ruanyifeng.com/ let 和 const 命令 1. let 命令 尽量使用 let 声明变量&#xff0c;而不是 var。let 声明的变量是块级作用域&#xff0c; var 声明的变量是全局作用域。使用 let 变量必须先声明再使用&#…

WebRTC实现1对1音视频通信原理

什么是 WebRTC &#xff1f; WebRTC&#xff08;Web Real-Time Communication&#xff09;是 Google于2010以6829万美元从 Global IP Solutions 公司购买&#xff0c;并于2024年01月10日将其开源&#xff0c;旨在建立一个互联网浏览器间的实时通信的平台&#xff0c;让 WebRTC…

如何使用PHP开发缓存优化图片加载速度

一淘模板发现随着互联网的快速发展&#xff0c;网页加载速度成为用户体验的重要因素之一。而图片加速是影响网页加载速度的重要因素之一。为了加速图片的加载&#xff0c;我们可以使用PHP开发缓存来优化图片加载速度。本文将介绍如何使用PHP开发缓存来优化图片加载速度&#xf…

Java-文件操作-FAQ-新建目录

1 需求 需求1&#xff1a;如果目录不存在新建目录&#xff1b; 需求2&#xff1a;如果目录存在&#xff0c;先删除目录&#xff0c;再新建目录&#xff1b; 2 接口 File类 mkdirmkdirsFiles类 createDirectories 3 示例&#xff1a;File类创建目录 import java.io.File;pu…

计算机毕业设计---ssm实验室设备管理系统

项目介绍 ssm实验室设备管理系统。前台jsplayuieasyui等框架渲染数据、后台java语言搭配ssm(spring、springmvc、mybatis、maven) 数据库mysql8.0。该系统主要分三种角色&#xff1a;管理员、教师、学生。主要功能学校实验设备的借、还、修以及实验课程的发布等等&#xff1b;…

windows配置电脑网络IP的方法

通过控制面板配置IP地址&#xff1a; 打开控制面板&#xff1a; 可以通过在开始菜单中搜索“控制面板”来打开控制面板。选择“网络和Internet”或“网络和共享中心”&#xff1a; 在控制面板中&#xff0c;根据 Windows 版本不同&#xff0c;选中对应的选项进入网络设置。点击…

再不收藏就晚了,Axure RP Pro 各版本大集合

Axure RP Pro下载链接 https://pan.baidu.com/s/1hRJRY6t0ZONKhdwvykAc3g?pwd0531 1.鼠标右击【Axure RP Pro9.0】压缩包&#xff08;win11及以上系统需先点击“显示更多选项”&#xff09;选择【解压到 Axure RP Pro9.0】。 2.打开解压后的文件夹&#xff0c;鼠标右击【Axu…

2024啦,致敬最可爱的技术人!!

大家可以关注我的公众号和视频号“架构随笔录”。 ​作为一个开源爱好者&#xff0c;我花费了大概1整天的时间去整理了国内外主流的互联网公司在Java后端领域的开源输出成果&#xff0c;顿时感悟太多&#xff0c;总是觉得这些贡献开源的技术人及对应技术公司确实太不容易了&am…

Linux ls命令用法

Linux ls&#xff08;英文全拼&#xff1a; list directory contents&#xff09;命令用于显示指定工作目录下之内容&#xff08;列出目前工作目录所含的文件及子目录)。 语法 ls [-alrtAFR] [name...] 参数 : -a 显示所有文件及目录 (. 开头的隐藏文件也会列出)-d 只列出目…

golang并发安全-select

前面说了golang的channel&#xff0c; 今天我们看看golang select 是怎么实现的。 数据结构 type scase struct {c *hchan // chanelem unsafe.Pointer // 数据 } select 非默认的case 中都是处理channel 的 接受和发送&#xff0c;所有scase 结构体中c是用来存储…

前端monorepo大仓权限设计的思考与实现

一、背景 前端 monorepo 在试行大仓研发流程过程中&#xff0c;已经包含了多个业务域的应用、共享组件库、工具函数等多种静态资源&#xff0c;在实现包括代码共享、依赖管理的便捷性以及更好的团队协作的时候&#xff0c;也面临大仓代码文件权限的问题。如何让不同业务域的研…