优选算法的时序之窗:滑动窗口专题(一)

优选算法的时序之窗:滑动窗口专题(一)

专栏:算法的魔法世界

个人主页:手握风云

目录

一、滑动窗口

二、例题讲解

2.1. 长度最小的子数组

2.2. 无重复字符的最长子串

2.3. 水果成篮

2.4. 将 x 减到 0 的最小操作

一、滑动窗口

        滑动窗口算法又叫“同向双指针算法”,核心在于维护一个窗口,这个窗口的左右边界可以根据需要动态调整。窗口的大小通常取决于当前窗口内的元素是否满足某种条件。通过移动窗口的左右边界,可以在一次遍历中找到满足条件的子数组或子串。

二、例题讲解

2.1. 长度最小的子数组

       题目要求找出长度最小的子数组,并且子数组的和大于等于目标值。暴力枚举策略,就是找出所有和大于等于目标值的子数组,我们需要找出子数组的左右区间,还要遍历一遍子数组求和,那么时间复杂度为

O(n^{3})

       接下来对暴力枚举进行优化。题目中给出了条件,数组里的元素都是正整数。我们可以定义两个指针left和right,让right向右移动,left和right就可以构成一个子数组。当子数组的和大于等于目标值时就固定,让left指针也向右移动。因为数组是具有单调性的,两个指针不用回退,就可以省去很多不必要的枚举,让子数组里的元素不停进出,维护更新子数组的和。我们操作只有right和left的移动,那么时间复杂度为

O(n)

代码实现:

class Solution{ public int minSubArrayLen(int target,int[] nums){ int sum = 0,len = Integer.MAX_VALUE, for(int left = 0,right = 0; right < nums.length; right++){ sum += nums[right];//进窗口 while(sum >= target){ len = Math.min(len,right-left+1); sum -= nums[left++]; } } return len == Integer.MAX_VALUE ? 0 : len;//如果给定的数组和小于目标值,直接返回0 } }

2.2. 无重复字符的最长子串

       第一种解法,使用暴力枚举,即找出所有不含重复字符的子串,再比较长度大小。那我们如何知道字符重复出现呢?我们在这里可以使用哈希表,同样是定义两个指针left和right,让right把遍历过的字符扔进哈希表里面,此时我们就需要遍历两遍子串,时间复杂度为

O(n^{2})

       当right遇到重复的字符时,left向右移动。按照暴力枚举的策略,right还需要回来,再次向右移动,这是没有必要了。因为left与right之间还有与right指向相同的字符,那么left指针此时就可以直接跳过了。由于两个指针也是同向移动的,就可以利用滑动窗口来解决。

       滑动窗口的解决过程就是“进窗口→判断→出窗口→更新结果”。“进窗口”就是将字符扔进哈希表中;“判断”的过程就是看看哈希表中是否存在重复的字符;“出窗口”就是从哈希表中删除重复的字符。

        完整代码实现:

class Solution{ public int lengthOfLongestSubstring(String s){ char[] str = s.toCharArray();//将字符串转化为字符数组 int hash = new int[128];//用数组模拟哈希表 int left = 0,right = 0,n = s.length; int ret = 0; while(right < n){ hash[ss[right]]++;//进入窗口 while (hash[ss[right]] > 1){//判断 hash[ss[left++]]--;//出窗口 } ret = Math.max(ret,right-left+1);//更新结果 right++;//让下一个字符进入窗口 } return ret; } }

2.3. 水果成篮

        题目中给出我们只有两个篮子,每个篮子只能装一种水果,可以从任意一棵树开始,中间不能越过某一棵树,求收集的水果的最大数目。我们看下面的测试用例,我们可以采摘[0,1]两棵树,也可以采摘[1,2,2]三棵树。也就是题目要求我们求出给定数组的最长子数组,并且子数组中只含有两种数字。

        我们如何知道子数组里面只有两种水果,可以利用哈希表来存储水果的种类数量。我们取出一段子数组,里面的水果种类kinds恰好为2,此时让left向右移动,会出现两种结果:kinds不变,right也不需要向右移动;kinds减小,则right向右移动,让子数组中的水果种类再次增加为2。

        通过上面的示例分析,我们可以得出双指针是通向移动的,照样使用滑动窗口来解决问题。“进窗口”,把水果扔进哈希表中。“判断”,当哈希表的长度大于2时,那么这个窗口不是合法的,就得让left向右移动。但我们不知道left需要移动到哪里,那我们的哈希表就不能只定义一个元素种类,还有一个元素的数量。然后“出窗口”,把哈希表里的元素删除,让窗口变得合法。

        完整代码实现:

class Solution { public int totalFruit(int[] fruits) { Map<Integer,Integer> hash = new HashMap<>();//统计窗口内的水果种类 int ret = 0; for (int left = 0,right = 0;right < fruits.length;right++){ int in = fruits[right]; hash.put(in,hash.getOrDefault(in,0)+1);//进窗口 while(hash.size() > 2){ int out = fruits[left]; hash.put(out,hash.get(out)-1);//出窗口 if(hash.get( out) == 0){ hash.remove(out); } left++; } ret = Math.max(ret,right-left+1); } return ret; } }

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

        对于上面的测试用例,我们可以移除“1、1、3”,此时操作数是3次;我们也可以移除“3、2”,此时的最小操作数为2。如果我们直接想这道题会非常困难,因为我们不知道是先删左边还是先闪右边。那如果我们反过来思考,从数组当中找出一段最长的子数组,这个最长子数组的和target就是给定数组的和sum减去x的值。

        我们先定义两个指针left和right,当right指针移动到某个下标时,子数组的和小于sum,如果right再向右移动一位,此时恰好target大于等于sum。之后我们再让left指针向右移动,此时我们需要思考一下,right是否需要回退?是不需要的(如下图所示),right要么不动,要么向右移动,此时又符合同向双指针的解法,也就是可以使用滑动窗口。

        “进窗口”,right指针向右移动,计算子数组的和;“判断”,当子数组的和大于target时(这里不写等于,是为了防止代码书写混乱的),left指针向左移动,完成“出窗口”的操作;最后更新结果,找到最长的子数组。

        完整代码实现:

class Solution { public int minOperations(int[] nums, int x) { int sum = 0,len = nums.length; for (int i = 0; i < len; i++) { sum += nums[i]; }//求总数组的和 int target = sum - x; if(target < 0) return -1; int ret = -1; for (int left = 0,right = 0,tmp = 0;right < len;right++){ tmp += nums[right];//进窗口 while(tmp > target)//判断 tmp -= nums[left++];//出窗口 if(tmp == target) ret = Math.max(ret,right - left + 1);//更新结果 } if(ret == -1) return ret; else return len - ret; } }

Read more

【笔记】Trae+Andrioid Studio+Kotlin开发安卓WebView应用

【笔记】Trae+Andrioid Studio+Kotlin开发安卓WebView应用

文章目录 * 简介 * 依赖 * 步骤 * AS(Andriod Studio)创建项目 * AS创建虚拟机 * TRAE CN 修改项目 * 新增按键捕获功能 * 新增WebView * WebView加载本地资源 * 在按键回调中向WebView注入JS代码 * 最终关键代码 * 吐槽 简介 使用Trae配合Andriod Studio开发一个内嵌WebView的安卓应用, 在WebView中加载本地资源, 在APP中捕获按键事件对WebView中的内容进行操作; 依赖 * Trae CN (https://www.trae.com.cn/) * Andriod Studio (https://developer.android.google.cn/studio?hl=zh-cn), 以下简称AS * 吃内存, 占用了我大约6GB内存 * 下载项目依赖和安卓虚拟机(约2GB)依赖网络 * 基础的编程知识 步骤 AS(

By Ne0inhk
前端拖拽排序实现详解:从原理到实践 - 附完整代码

前端拖拽排序实现详解:从原理到实践 - 附完整代码

🌷 古之立大事者,不惟有超世之才,亦必有坚忍不拔之志 🎐 个人CSND主页——Micro麦可乐的博客 🐥《Docker实操教程》专栏以最新的Centos版本为基础进行Docker实操教程,入门到实战 🌺《RabbitMQ》专栏19年编写主要介绍使用JAVA开发RabbitMQ的系列教程,从基础知识到项目实战 🌸《设计模式》专栏以实际的生活场景为案例进行讲解,让大家对设计模式有一个更清晰的理解 🌛《开源项目》本专栏主要介绍目前热门的开源项目,带大家快速了解并轻松上手使用 🍎 《前端技术》专栏以实战为主介绍日常开发中前端应用的一些功能以及技巧,均附有完整的代码示例 ✨《开发技巧》本专栏包含了各种系统的设计原理以及注意事项,并分享一些日常开发的功能小技巧 💕《Jenkins实战》专栏主要介绍Jenkins+Docker的实战教程,让你快速掌握项目CI/CD,是2024年最新的实战教程 🌞《Spring Boot》专栏主要介绍我们日常工作项目中经常应用到的功能以及技巧,代码样例完整 👍《Spring Security》专栏中我们将逐步深入Spring Security的各个

By Ne0inhk
在Ubuntu 20.04上安装Ollama并部署大型语言模型:含Open WebUI图形界面教程

在Ubuntu 20.04上安装Ollama并部署大型语言模型:含Open WebUI图形界面教程

在Ubuntu 20.04上安装Ollama并部署大型语言模型:含Open WebUI图形界面教程 引言 在人工智能浪潮席卷全球的今天,大型语言模型(LLM)不再是遥不可及的云端技术。借助 Ollama,每一位开发者都能轻松地将强大的模型部署在自己的本地计算机上,实现无缝、私密且可定制的AI体验。本文将带领您一步步在 Ubuntu 20.04 系统上完成 Ollama 的安装与模型部署,并最终搭建美观易用的图形化界面(Open webui)。 Ollama 是什么? Ollama 是一个开源项目,专为在本地运行、管理和部署大型语言模型(如 Llama 3、Mistral、Gemma 等)而设计。 它的核心概念与优势非常清晰: * 简单易用:通过简单的命令行工具,即可完成模型的下载(pull)、运行(run)和管理。一条命令就能启动与模型的对话。 * 丰富的模型库:它提供了官方支持的模型库(Ollama

By Ne0inhk
前端核心知识:Vue 3 编程的 10 个实用技巧

前端核心知识:Vue 3 编程的 10 个实用技巧

文章目录 * 1. **使用 `ref` 和 `reactive` 管理响应式数据** * 原理解析 * 代码示例 * 注意事项 * 2. **组合式 API(Composition API)** * 原理解析 * 代码示例 * 优势 * 3. **使用 `watch` 和 `watchEffect` 监听数据变化** * 原理解析 * 代码示例 * 注意事项 * 4. **使用 `provide` 和 `inject` 实现跨组件通信** * 原理解析 * 代码示例 * 优势 * 5. **使用 `Teleport` 实现组件挂载到任意位置** * 原理解析 * 代码示例 * 优势 * 6. **使用 `Suspense` 处理异步组件加载** * 原理解析 * 代码示例 * 优势

By Ne0inhk