【Java算法】滑动窗口 上

  🔥个人主页: 中草药

🔥专栏:【算法工作坊】算法实战揭秘


👖一. 长度最小的子数组

题目链接:209.长度最小的子数组

算法原理

滑动窗口

滑动窗口算法常用于处理数组/字符串等序列问题,通过定义一个窗口(即一段连续的元素),并根据某种条件动态调整窗口的左右边界来找到满足条件的子序列。

在这个问题中,滑动窗口的具体应用如下:

  1. 初始化:设置两个指针 leftright,均初始化为0,表示窗口的左右边界。同时,初始化 sum 为0,用于累计窗口内元素的和,minlen 设置为 Integer.MAX_VALUE,用于记录符合条件的最小子数组长度。

  2. 扩展右边界:不断将右指针 right 向右移动,并将 nums[right] 加入到 sum 中,这相当于不断扩大窗口右侧,尝试包含更多的元素以满足和至少为 target 的条件。

  3. 收缩左边界:当窗口内的元素和 sum 至少达到 target 时,开始尝试缩小窗口左侧,即增加 left 指针的值,并从 sum 中减去移出窗口的元素 nums[left]。这个过程会持续进行,直到 sum 小于 target,确保每次收缩都检查当前窗口是否仍然满足条件。

  4. 更新最小长度:在每次收缩窗口后(即 sum 小于等于 target 时),用当前窗口的长度(right - left + 1)更新 minlen,保持记录最短的满足条件的子数组长度。

  5. 遍历结束:当右指针遍历完整个数组后,检查 minlen 是否仍为初始值 Integer.MAX_VALUE,如果是,则说明没有找到满足条件的子数组,返回0;否则返回 minlen 作为结果。

时间复杂度与空间复杂度

  • 时间复杂度:O(n),其中n为数组 nums 的长度。每个元素最多被访问两次,一次作为窗口扩展时加入,一次作为窗口收缩时移除。
  • 空间复杂度:O(1),只使用了固定数量的变量,与输入数组大小无关。

综上所述,这段代码利用滑动窗口算法高效地解决了寻找最小长度子数组使其和至少为目标值的问题。

代码

public int minSubArrayLen(int target, int[] nums) {int sum=0,minlen=Integer.MAX_VALUE;//无穷大for(int left=0,right=0;right<nums.length;right++){sum+=nums[right];//进窗口while(sum>=target){//出窗口minlen=Math.min(minlen,right-left+1);sum-=nums[left++];}}if(minlen==Integer.MAX_VALUE){return 0;}return minlen;}

举例

测试用例 [2,3,1,2,4,3]

初始状态

  • sum = 0
  • minlen = Integer.MAX_VALUE
  • 初始化双指针:left = 0right = 0

执行过程

第一轮迭代

  • right=0sum += nums[0] = 2, 现在 sum = 2, 不满足 sum >= targetright++
  • right=1sum += nums[1] = 3, 现在 sum = 5, 不满足 sum >= targetright++
  • right=2sum += nums[2] = 1, 现在 sum = 6, 不满足 sum >= targetright++
  • right=3sum += nums[3] = 2, 现在 sum = 8, 满足 sum >= target

这时进入内部的 while 循环。

第一次窗口收缩

  • sum >= target,计算子数组长度 right-left+1 = 4,更新 minlen = Math.min(minlen, 4) = 4
  • 从 sum 中减去 nums[left++] = nums[0] = 2,得到 sum = 6,继续在循环内检查。
  • 再次检查 sum >= target,是的,所以继续收缩。
  • 从 sum 中减去 nums[left++] = nums[1] = 3,得到 sum = 3,现在 sum < target,退出 while 循环。

接下来

  • 继续移动 right,累加 sum 直至再次满足条件或遍历完数组。

第二次满足条件

  • right=4sum += nums[4] = 4,现在 sum = 7,满足 sum >= target
  • 计算子数组长度 right-left+1 = 2,更新 minlen = Math.min(minlen, 2) = 2
  • 由于这次 sum 正好等于 target,收缩窗口会导致 sum < target,因此不会进一步收缩。

遍历完成

  • 继续移动 right,但不再有新的子数组满足条件且长度更小。

结果

遍历结束后,minlen = 2,这是满足条件的最小子数组长度,对应子数组是 [4,3]

🩰二.无重复字符的最长子串

题目链接:3.无重复字符的最长子串

算法原理

  1. 初始化:定义两个指针 leftright,初始都指向字符串的起始位置,即 left = 0right = 0hash 数组用于记录 ASCII 字符出现的次数(考虑到 ASCII 总共有 128 个字符,故数组大小为 128)。ret 用于存储最长无重复子串的长度,初始化为 0。

  2. 扩展右边界:不断地将右指针 right 向右移动,每移动一次,就将当前字符 ch[right]hash 数组中的计数加一。这意味着窗口正在尝试包含更多的字符。

  3. 处理重复字符与收缩左边界:如果 hash[ch[right]] 大于 1,表明当前字符 ch[right] 已经在窗口内出现过,这时需要移动左指针 left,将 left 指向的字符在 hash 中的计数减一,同时左指针右移一位,以此来排除重复字符,保证窗口内的字符都是唯一的。

  4. 更新最大长度:每次移动右指针时,都计算当前窗口的长度(即 right - left + 1),并将这个长度与当前已知的最大长度 ret 进行比较,取较大者更新 ret。这样可以保证 ret 始终保存着遇到过的最长无重复字符子串的长度。

  5. 遍历结束:当右指针 right 遍历到字符串末尾时,循环结束,返回 ret 作为最终结果。

时间复杂度与空间复杂度

  • 时间复杂度:O(n),其中 n 是字符串的长度。每个字符最多被访问两次,一次作为右边界扩展,一次作为左边界收缩。
  • 空间复杂度:O(1),虽然使用了 hash 数组,但它是一个固定大小的数组(128),与输入字符串的长度无关,因此空间复杂度是常数级别的。

总之,这段代码通过巧妙地利用滑动窗口和哈希表(在这里是简化版的数组实现)减少了不必要的字符比较,从而高效地求解了最长无重复字符子串的长度问题。

代码

 public int lengthOfLongestSubstring(String s) {int left=0,right=0;char[] ch=s.toCharArray();int[] hash=new int[128];//用数组模拟哈希表int ret=0;while(right<s.length()){hash[ch[right]]++;while(hash[ch[right]]>1){hash[ch[left++]]--;}ret =Math.max(ret,right-left+1);right++;}return ret;}

 举例

测试用例 "abcabcbb"

初始化

  • left = 0right = 0
  • char[] ch = {'a', 'b', 'c', 'a', 'b', 'c', 'b', 'b'}
  • int[] hash 初始化为全0,用于记录字符出现次数
  • ret = 0,用于记录最长无重复子串长度

执行过程

  1. 右指针移动 & 记录字符

    • right=0'a'hash['a'] = 1right++
    • right=1'b'hash['b'] = 1right++
    • right=2'c'hash['c'] = 1right++。此时,窗口abc无重复字符,ret = 3
  2. 发现重复,收缩左边界

    • right=3'a'hash['a'] = 2,发现重复,开始收缩左边界,hash['a']--left++
  3. 继续扩展与收缩

    • right=4'b'hash['b'] = 2,收缩左边界,hash['b']--left++
    • right=5'c'hash['c'] = 2,收缩左边界,hash['c']--left++。此时窗口内为bc,无重复字符,ret保持为3
  4. 重复与扩张

    • right=6'b'hash['b'] = 3,收缩左边界,hash['b']--left++
    • right=7'b'hash['b'] = 3,继续收缩左边界,hash['b']--left++。此时窗口为空,ret保持为3

结果

  • 最后,所有字符遍历完毕,返回 ret 的值,即最长无重复字符的子串长度为 3。在这段字符串中,满足条件的最长子串是 "abc"

这段代码通过动态调整窗口(由 leftright 定义)大小,有效利用哈希表(此处为简化版的 hash 数组)快速判断字符是否重复,最终找到了最长的无重复字符子串。

🎩三.最大连续1的个数III

题目链接:1004.最大连续1的个数III

算法原理

  1. 初始化:定义三个指针 left, right, 和一个计数器 zero,分别用来表示窗口的左边界、右边界以及窗口内 0 的数量。同时,定义 ret 来存储最长子数组的长度,初始化为 0。

  2. 扩展右边界:不断将右指针 right 向右移动,每次移动时检查新进入窗口的元素(nums[right])是否为 0,如果是,则将 zero 计数器加 1。这意味着窗口正在尝试包含更多的元素,包括 0。

  3. 处理限制条件:当窗口内 0 的数量超过了允许的最大数量 k 时,需要移动左指针 left 来缩小窗口,直到窗口内 0 的数量回到 k 或更少。在移动 left 的过程中,如果移出窗口的元素是 0,则将 zero 减 1。

  4. 更新最长子数组长度:在每次移动右指针之后(即每次尝试扩大窗口或维持窗口大小但可能更新了窗口内容后),都会用当前的右指针减去左指针再加 1 来计算当前窗口的长度,并用这个长度去更新 ret 的值,保持 ret 存储的是最长子数组的长度。

  5. 遍历结束:当右指针遍历完整个数组后,循环结束,此时 ret 中存储的就是满足条件的最长连续子数组的长度。

时间复杂度与空间复杂度

  • 时间复杂度:O(n),其中 n 是数组 nums 的长度。每个元素最多被遍历一次。
  • 空间复杂度:O(1),因为使用的变量数量是固定的,不依赖于输入数组的大小。

代码

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,1,1,0,0,0,1,1,1,1,0]

初始化

  • left = 0right = 0zero = 0ret = 0

执行过程

  1. 右指针移动:初始化时数组内的前三个元素都是1,右指针向右移动,窗口内没有0,zero 仍为0,此时最长子数组长度为3,但随着右指针的移动,情况会变化。

  2. 遇到0并处理

    • 当 right = 3,遇到第一个0,zero = 1
    • 当 right = 4,遇到第二个0,zero = 2,达到了 k = 2 的限制。
    • 此时窗口 [1,1,1,0,0],满足条件,长度为5,更新 ret = 5
  3. 收缩左边界

    • 当 right = 5,遇到第三个0,由于 zero 已经是2,需要移动左指针。当 left = 3nums[left++] 是0,zero--,此时窗口内0的个数回到1,窗口变为 [1,0,0,1,0,0]
  4. 继续扩展右边界

    • 继续移动右指针,忽略更多的0,直到 right = 8,窗口为 [0,0,1,1,1,1,1,1],此时 zero = 2(最初的两个0),窗口长度为6,更新 ret = 6
  5. 之后的步骤

    • 当 right 继续移动并遇到更多0时,由于已经找到了长度为6且满足条件的子数组,即使右边界继续扩大,也不会影响最终答案,因为任何新加入的0都会导致左侧边界相应移动,保持最多2个0在窗口内。

结论

  • 最终返回 ret = 6,表示最长的连续子数组是 [0,0,1,1,1,1] 

🧢四.将x减到0的最小操作数

题目链接:11658.将x减到0的最小操作数

算法原理

  1. 计算总和:首先遍历数组 nums,计算其所有元素之和 sum。如果 sum 小于目标值 x,直接返回 -1,因为无论如何操作都无法使子数组之和等于 x

  2. 确定目标值:需要找到一个和为目标 x 的子数组,但实际操作中,我们是在找一个和为 target = sum - x 的子数组。这是因为我们需要从总和中减去 x 来达到操作的目的。

  3. 双指针滑动窗口

    • 初始化两个指针 left 和 right 都为 0,同时维护一个变量 cur 表示当前窗口内的元素和。
    • 右指针 right 向右移动,将 nums[right] 加入当前窗口的和 cur
    • 如果 cur 大于目标值 target,则说明当前窗口和过大,需要减小窗口左侧的值,即减去 nums[left] 并将 left 指针右移一位,同时更新 cur
    • 当 cur 等于 target 时,说明找到了一个符合条件的子数组。此时更新最大长度 ret 为当前子数组的长度(right-left+1)。
    • 继续移动右指针,重复上述过程,直到右指针遍历完整个数组。
  4. 结果处理

    • 如果在整个过程中没有找到符合条件的子数组(即 ret 仍为初始值 -1),返回 -1
    • 若找到了符合条件的子数组,返回数组长度减去找到的最长子数组长度,即 nums.length - ret。这代表需要的操作次数,因为要使得剩下的部分和为 x,整个数组的剩余部分(非连续子数组)需要通过减少操作来匹配。

时间复杂度与空间复杂度:

  • 时间复杂度:O(n),其中 n 是数组 nums 的长度。每个元素最多被访问两次,一次在计算总和时,一次在滑动窗口中。
  • 空间复杂度:O(1),使用的额外空间与输入数组的大小无关,只使用了几个固定变量。

代码

 public int minOperations(int[] nums, int x) {int sum=0;int ret=-1;for (int a : nums){sum+=a;}if(sum<x){return -1;}int target=sum-x;for (int left=0,right=0,cur=0;right<nums.length;right++){cur+=nums[right];while(cur>target){cur-=nums[left++];}if (cur==target){ret=Math.max(ret,right-left+1);}}if(ret==-1){return -1;}return nums.length-ret;}

举例

测试用例 nums=[1,1,4,2,3],x=5

初始化

  • 首先,遍历数组 nums 计算所有元素之和 sum

    • sum = 1 + 1 + 4 + 2 + 3 = 11
  • 检查 sum 是否小于 x,若小于则直接返回 -1,这里不适用,因为 11 >= 5

  • 计算目标和 targetsum - x

    • target = 11 - 5 = 6

滑动窗口

接下来,使用双指针(leftright)进行滑动窗口操作,同时维护窗口内元素和 cur

  1. 初始化left = 0, right = 0, cur = 0

  2. 遍历数组

    • 右指针移动right 开始从 0 向右移动,每次移动将 nums[right] 加入 cur
      • 当 right = 0cur = 0 + 1 = 1(此时 cur < target
      • 当 right = 1cur = 1 + 1 = 2(此时 cur < target
      • 当 right = 2cur = 2 + 4 = 6,此时 cur == target,记录子数组长度,ret = Math.max(ret, right-left+1) = Math.max(-1, 3) = 3,并且开始收缩窗口。
      • 由于 cur 等于 target,无需移动 left
  3. 继续移动右指针

    • 当 right = 3cur = 6 + 2 = 8(此时 cur > target),需要收缩窗口。
      • 移动 leftcur -= nums[left++] = 8 - 1 = 7left = 1,此时 cur > target 继续移动 left
      • 再次移动 leftcur -= nums[left++] = 7 - 1 = 6left = 2,此时 cur == target,不需要进一步移动 left
  4. 遍历结束

    • 右指针继续移动,但在本次测试用例中,不会再次找到满足条件的子数组,因此 ret 保持为 3

结果处理

  • 最后,检查 ret 是否为 -1,如果不是,则返回 nums.length - ret,因为这是从整个数组长度中减去满足条件的子数组长度,即需要操作的次数。
    • return nums.length - ret = 5 - 3 = 2

结论

对于测试用例 nums=[1,1,4,2,3]x=5,这段代码会返回 2,意味着需要至少操作两次,使得数组中某一段连续子数组之和等于 x=5。具体来说,可以通过减少前两个元素各一次(即 1-1=01-1=0),使得剩下的数组 [0,0,4,2,3]4+2=6 符合要求。


🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀

以上,就是本期的全部内容啦,若有错误疏忽希望各位大佬及时指出💐

  制作不易,希望能对各位提供微小的帮助,可否留下你免费的赞呢🌸

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

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

相关文章

Java赋值运算符

Java赋值运算符分为以下&#xff1a; 符号 作用 说明 赋值 int a 10,把10赋值给变量a 加后赋值 ab,将ab的值赋值给变量a - 减后赋值 a-b,将a-b的值赋值给变量a* 乘后赋值 a*b,将a*b的值赋值给变量a / 除后赋值 a/b,将a/b的值赋值给变量a % 取余赋值 a%b,将a%b的值赋值给变量…

贪心算法—

贪心算法是一种在每一步选择中都采取在当前状态下最好或最优&#xff08;即最有利&#xff09;的选择&#xff0c;从而希望导致结果是全局最好或最优的算法。这种算法并不总是能找到全局最优解&#xff0c;但在某些问题上能提供足够好的解决方案。贪心算法的关键特性包括&#…

JR-8000系列机架式多路4K超高清光端机

集中式 4K超高清光传输设备 1 产品特性 ⚫ 支持高达 8 通道 SMPTE 全格式 SDI 信号输入 ⚫ 发送端带有 LOOPOUT 环出端口&#xff0c;具备消抖动功能&#xff0c;可作为信号调理或级联信号源使用 ⚫ 接收端支持双输出端口 ⚫ 支持传输速率&#xff1a;143Mbps-11.88Gbps ⚫…

Intel太无耻,跟着玩数字游戏还揭台积电的老底,工艺都是假的

在台积电的3纳米逐渐获得芯片企业认可的情况下&#xff0c;近日Intel却再次指出台积电的3纳米工艺并非真正的3纳米&#xff0c;与Intel的7纳米工艺差不多&#xff0c;这显示出Intel在芯片工艺研发方面日益落后的情况下确实有点慌了。 Intel指出它的7纳米工艺的晶体管密度达到1.…

python watchdog 配置文件热更新

目录 一、Watchdog示例 二、aiohttp服务配置热更新 在同事的golang代码中学习到了config.json热更新的功能&#xff0c;这里自己也学习了一下python写web服务的时候怎么来实现配置的热更新。主要是利用Watchdog这个第三方python库&#xff0c;来监控文件系统的改变&#xff0…

学习使用venv创建“python虚拟环境”

前言 使用python开发会经常面临的问题是&#xff1a;你会需要不同版本的python&#xff0c;而且就算同一个版本的python&#xff0c;不同的项目有很大可能会需要不同版本的包。而 “Python虚拟环境” 就是为了解决这个问题的。 目标 结合官方文档&#xff0c;自己动手实践来…

开启声音的奇幻之旅:AI声音变换器的魔法秘籍与创意应用

AI视频生成&#xff1a;小说文案智能分镜智能识别角色和场景批量Ai绘图自动配音添加音乐一键合成视频https://aitools.jurilu.com/这个充满科技魔力的时代&#xff0c;AI Voice Changer 就像一把神奇的钥匙&#xff0c;能为我们打开声音的魔法之门。今天&#xff0c;就让我带你…

JetBrains PyCharm 2024 mac/win版编程艺术,智慧新篇

JetBrains PyCharm 2024是一款功能强大的Python集成开发环境(IDE)&#xff0c;专为提升开发者的编程效率和体验而设计。这款IDE不仅继承了前代版本的优秀特性&#xff0c;还在多个方面进行了创新和改进&#xff0c;为Python开发者带来了全新的工作体验。 JetBrains PyCharm 20…

腰背肌筋膜炎怎么治疗最有效

腰背肌筋膜炎的治疗方法主要包括以下几种&#xff1a; 1、休息和物理治疗&#xff1a; 确保充足的休息&#xff0c;避免过度劳累&#xff0c;减少腰背部肌肉的负担。 物理治疗&#xff0c;如热敷或冷敷&#xff0c;可以缓解疼痛和肌肉紧张。热敷可以使用热水袋、热毛巾或电热垫…

linux普通: rocketmq的安装测试与可视化界面安装,git的 (linux) 安装

全文目录,一步到位 1.前言简介1.1 专栏传送门(rabbitmq) 2. rocketmq使用及安装2.0 开放端口2.1 rocketmq版本说明2.2 具体操作2.2.1 修改文件2.2.2 具体启动指令ps: 查看日志 2.3.3 jps查看java进程2.3.4 测试运行情况> 步骤一: 临时指定nameserver注册中心位置> 步骤二…

【机器学习】基于Softmax松弛技术的离散数据采样

1.引言 1.1.离散数据采样的意义 离散数据采样在深度学习中起着至关重要的作用&#xff0c;它直接影响到模型的性能、泛化能力、训练效率、鲁棒性和解释性。 首先&#xff0c;采样方法能够有效地平衡数据集中不同类别的样本数量&#xff0c;使得模型在训练时能够更均衡地学习…

实用软件下载:会声会影2023最新安装包及详细安装教程

可以说它不仅符合家庭或个人所需的影片剪辑功能&#xff0c;甚至能够挑战专业级的影片剪辑软件&#xff0c;适合一般大众使用&#xff0c;操作简单易懂&#xff0c;界面简洁明快。从总体上来看影片制作向导模式&#xff0c;只要三个步骤就可快速做出DV影片&#xff0c;入门初学…

乌班图Ubuntu 24.04 SSH Server 修改默认端口重启无效

试用最新的乌班图版本&#xff0c;常规修改ssh端口&#xff0c;修改完毕后重启sshd提示没有找到service&#xff0c;然后尝试去掉d重启ssh后查看状态&#xff0c;端口仍然是默认的22&#xff0c;各种尝试都试了不行&#xff0c;重启服务器后倒是端口修改成功了&#xff0c;心想…

推出一系列GaN功率放大器: QPA2211、QPA2211D、QPA2212、QPA2212D、QPA2212T,支持卫星通信和5G基础设施。

推出用于支持支持卫星通信和5G基础设施的GaN功率放大器&#xff1a; QPA2211 QPA2211D QPA2212 QPA2212D QPA2212T QPA2211 10W GaN功率放大器是一款Ka波段功率放大器&#xff0c;采用0.15m碳化硅基氮化镓工艺 (QGaN15) 制造而成。该放大器的工作频率范围为27.5GHz至31GHz&…

【JUC并发编程】

Java并发常见面试题总结&#xff08;上&#xff09; 线程 什么是线程和进程? 何为进程? 进程是程序的一次执行过程&#xff0c;是系统运行程序的基本单位&#xff0c;因此进程是动态的。系统运行一个程序即是一个进程从创建&#xff0c;运行到消亡的过程。 在 Java 中&am…

基于SpringBoot+Vue教材订购系统设计和实现(源码+LW+调试文档+讲解等)

&#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN作者、博客专家、全栈领域优质创作者&#xff0c;博客之星、平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌&#x1f497; &#x1f31f;文末获取源码数据库&#x1f31f; 感兴趣的可以先收藏起来&#xff0c;…

文件上传漏洞-下篇

一、白名单绕过 目录路径检测绕过 00截断 简介&#xff1a; 0x00是字符串的结束标识符&#xff0c;攻击者可以利用手动添加字符串标识符的方式来将后面的内容进行截断&#xff0c;而后面的内容又可以帮助我们绕过检测。 饶过条件 利用操作&#xff1a;Pass-12 要求&#xff…

高考志愿填报,二个准备三个重点四个原则

对于高考生而言&#xff0c;高考完毕并不是可以轻松地开始&#xff0c;接下来需要研究怎么报考的问题。如何在理想和现实中取得平衡&#xff1f;如何根据就业和专业的前景做合适的安排&#xff0c;对于还处于青少年阶段的高考生们来说不是容易的事情&#xff0c;要掌握哪些技巧…

CANoe CAPL如何模拟发送CAN错误帧?

目录 canOutputErrorFrame介绍代码output(errorframe)代码总结canOutputErrorFrame 介绍 代码 canOutputErrorFrame(errorFrame, 12, 0); //output Error Frame with 12 dominant bits on CAN1 canOutputErrorFrame(CAN2.errorFrame, 6,

快手可灵大模型开放视频续写功能,可生成最长约3分钟视频

6月21日&#xff0c;可灵再度进化&#xff0c;正式推出图生视频功能&#xff0c;支持用任意静态图像生成5s视频&#xff0c;并且可搭配不同的文本内容&#xff0c;实现丰富的视觉叙事 。 同时&#xff0c;可灵还发布了业内领先的视频续写功能&#xff0c;可为已生成的视频&…