【leetcode】双指针算法技巧——滑动窗口

标题:【leetcode】双指针算法技巧——滑动窗口

@水墨不写bug 


正文开始:

 

滑动窗口介绍 

        滑动窗口是一种常用的算法技巧,用于解决一些涉及  连续子数组或子串  的问题。它的基本思想是  维护一个窗口,通过  在窗口内移动  来寻找满足特定条件的子数组或子串。

滑动窗口算法的步骤如下:

  1. 初始化窗口的起始位置left和终止位置right。
  2. 在每一步迭代中,将窗口右移一位,即将right加1,并根据题目要求更新窗口的状态。
  3. 如果窗口的状态满足题目给定的条件,那么根据题目要求处理结果。
  4. 如果窗口的状态不满足题目给定的条件,那么将窗口左移一位,即将left加1,继续迭代。

滑动窗口算法的时间复杂度通常为O(n),其中n是数组或字符串的长度。它的优势在于可以将一些问题的时间复杂度从O(n^2)降低到O(n)。

四个必要步骤 

滑动窗口的四个必要步骤:

        (1)入窗口

        (2)判断

        (3)出窗口

        (4)更新结果 

对于前三条,它们的顺序一般相对彼此是固定的。更新结果的位置放在何处,需要根据题意判断。


(一)长度最小的子数组

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

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

[nums , nums + 1, ... , nums + r-1, nums + r] ,并返回其长度如果不存在符合条件的子数组,返回 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 <= target <= 10^9
  • 1 <= nums.length <= 10^5
  • 1 <= nums[i] <= 10^5

进阶:

  • 如果你已经实现 O(n) 时间复杂度的解法, 请尝试设计一个 O(n log(n)) 时间复杂度的解法。

        思路一:

        找出所有的情况,遍历出所有sum==k的情况,分别求其长度,找出最小的长度即可。

以用例 1 具体来说:

[  2  ,  3  ,  1  ,  2  ,  4  ,  3  ]

        创建left指针标记第一个元素,创建一个right指针向后遍历,同时将两指针之间的元素求和sum,如果sum==k,将此时的len存入数组ret,最终再找出ret中的最小值即可。

        更优的算法从哪里来?是从暴力算法优化而来的!

        思路二:

        从宏观上我们先让sum尽可能的和k的值相近,这样才可能满足sum与k相等。假设我们此时确定了一个区间:

为了满足条件,

        如果sum < k,继续增加sun的值;

        如果sum==k,记录len(挑最小的记录);

        如果sum > k,则要减小sum的值;

现在就是确定如何加任何减的问题了。由于区间是连续的,不能有间断,所以从习惯上选择从左向右遍历,移动窗口。

        这样我们就知道了,

        left++,left指针向后移动实现减小sum(出窗口)

        right++,right指针向后移动实现增加sum(入窗口)

参考代码: 

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int n = nums.size(),len = INT_MAX,sum = 0;
        /*将len初始化为很大的数,防止被求最小值的实际长度干扰*/
        for(int left = 0,right = 0;right < n;++right)
        {
            //进窗口,sum维护
            sum += nums[right];
            while(sum >= target)//判断
            {
                
                len = min(len,right-left + 1);//更新结果
                sum -= nums[left++];//出窗口 
            }
        }
    return len == INT_MAX ? 0 : len;
    }
};

(二)无重复字符的最长字串

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

示例 1:

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

示例 2:

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

示例 3:

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

提示:

  • 0 <= s.length <= 5 * 10^4
  • s 由英文字母、数字、符号和空格组成

让滑动窗口满足:窗口内所有元素都是不重复的。


做法:右端元素 ch 进⼊窗口的时候,哈希表统计这个字符的频次:
▪ 如果这个字符出现的频次超过 1 ,说明窗口内有重复元素,那么就从左侧开始划出窗口,
直到 ch 这个元素的频次变为 1 ,然后再更新结果。
▪ 如果没有超过 1 ,说明当前窗口没有重复元素,可以直接更新结果。
 

参考代码:

class Solution {
public:
    int lengthOfLongestSubstring(string s) 
    {
        int left = 0,right = 0,n = s.size(),ret = 0;
        int hash[128] = {0};//使用数组模拟哈希表
        while(right < n)
        {
            hash[s[right]]++;//进入窗口
            //判断
            while(hash[s[right]] > 1)
            {
                hash[s[left++]]--;//出窗口
            }
            ret = max(ret,right - left + 1);
            right++;
        }
        return ret;
    }
};

(三)最大连续1的个数

给定一个二进制数组 nums 和一个整数 k,如果可以翻转最多 k 个 0 ,则返回 数组中连续 1 的最大个数 。

示例 1:

输入:nums = [1,1,1,0,0,0,1,1,1,1,0], K = 2
输出:6
解释:[1,1,1,0,0,1,1,1,1,1,1]
粗体数字从 0 翻转到 1,最长的子数组长度为 6。

示例 2:

输入:nums = [0,0,1,1,0,0,1,1,1,0,1,1,0,0,0,1,1,1,1], K = 3
输出:10
解释:[0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1]
粗体数字从 0 翻转到 1,最长的子数组长度为 10。

提示:

  • 1 <= nums.length <= 10^5
  • nums[i] 不是 0 就是 1
  • 0 <= k <= nums.length

        不要自找麻烦,思路其实很简单;

要换个角度思考->

        这道题的结果无非就是⼀段连续的 1 中间塞了 k 个 0 嘛。
        我们可以把问题转化成:求数组中一段最长的连续区间,要求这段区间内 0 的个数不超过 k 个。

参考代码: 


class Solution {
public:
    int longestOnes(vector<int>& nums, int k) {
        int hash[2] = {0};
        int n = nums.size(),ret = 0;
        //if(k >= n) return n;
        for(int right = 0,left = 0 ; right < n ; right++)
        {
            hash[ nums[right] ]++;//进窗口

            while(hash[0] > k)//判断
            {
                hash[ nums[left++] ]--;//出窗口
            }
            ret = max(ret,right-left+1);
        }
        return ret;
    }
};

(四)将x减小到0的最小操作数

给你一个整数数组 nums 和一个整数 x 。每一次操作时,你应当移除数组 nums 最左边或最右边的元素,然后从 x 中减去该元素的值。请注意,需要 修改 数组以供接下来的操作使用。

如果可以将 x 恰好 减到 0 ,返回 最小操作数 ;否则,返回 -1 。

示例 1:

输入:nums = [1,1,4,2,3], x = 5
输出:2
解释:最佳解决方案是移除后两个元素,将 x 减到 0 。

示例 2:

输入:nums = [5,6,7,8,9], x = 4
输出:-1

示例 3:

输入:nums = [3,2,20,1,1,3], x = 10
输出:5
解释:最佳解决方案是移除后三个元素和前两个元素(总共 5 次操作),将 x 减到 0 。

提示:

  • 1 <= nums.length <= 10^5
  • 1 <= nums[i] <= 10^4
  • 1 <= x <= 10^9

         题目要求的是数组「左端+右端」两段连续的、和为 x 的最短数组;

换一个角度思考->

        我们可以转化成求数组内⼀段连续的、和为 sum(nums) - x 的最长数组。此时,就是熟
悉的「滑动窗口」问题了
本质和 (一)长度最小的子数组 是相同的代码结构,就一点不同——需要记录的数组长度从记录最小变成了记录最大。

参考代码: 

class Solution {
public:
    int minOperations(vector<int>& nums, int x) {
        int sum_all = 0,n = nums.size();
        for(int i = 0;i < n;i++)
        {
            sum_all += nums[i];
        }
        int target = sum_all - x,sum = 0,left = 0,right = 0,ret = -1;
        
        if(target == 0) return n;
        for(;right < n;right++)
        {
            sum += nums[right];//进窗口
            if(sum > target)//判断,right最佳位置
            {
                while(sum > target && (left < right))
                {
                    sum -= nums[left++];//出窗口
                } 
            }
            if(sum == target)
            {
                ret = max(ret,right-left+1);
            }
            
        }
        if(ret == -1) return ret;
        else return n - ret;
    }
};

完~

未经作者同意禁止转载

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

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

相关文章

SD-WAN为什么能让网络运维更简单

在数字化浪潮席卷的今天&#xff0c;企业面临着网络规模的不断扩大、分支机构的广泛分布以及云服务需求的日益增长。企业目前的网络不足以达到这些要求&#xff0c;因而出现网络性能下降、运维复杂度剧增、成本攀升的问题。而SD-WAN&#xff08;软件定义广域网&#xff09;作为…

SASE:打造数据安全保障新模式

在企业纷纷拥抱数字业务的过程中&#xff0c;由于边缘计算、云服务、混合网络的逐渐兴起&#xff0c;使得本就漏洞百出的传统网络安全架构更加岌岌可危&#xff0c;而且远远无法满足企业数字业务的需要。 伴随企业全球化发展&#xff0c;企业的数据中心不再是用户与设备访问需…

socket编程——tcp

在我这篇博客&#xff1a;网络——socket编程中介绍了关于socket编程的一些必要的知识&#xff0c;以及介绍了使用套接字在udp协议下如何通信&#xff0c;这篇博客中&#xff0c;我将会介绍如何使用套接字以及tcp协议进行网络通信。 1. 前置准备 在进行编写代码之前&#xff…

Docker部署Prometheus+AlertManager实现邮件告警

文章目录 一、环境准备1、硬件准备&#xff08;虚拟机&#xff09;2、关闭防火墙&#xff0c;selinux3、所有主机安装docker 二、配置Prometheus1、docker启动Prometheus 三、添加监控节点1、docker启动node-exporter 四、Prometheus配置node-exporter1、修改prometheus.yml配置…

Docker构建Golang项目常见问题

Docker构建Golang项目常见问题 1 dockerfile报错&#xff1a;failed to read expected number of bytes: unexpected EOF2 go mod tidy: go.mod file indicates go 1.21, but maximum supported version is 1.17 1 dockerfile报错&#xff1a;failed to read expected number o…

鸿蒙语言TypeScript学习第18天:【泛型】

1、TypeScript 泛型 泛型&#xff08;Generics&#xff09;是一种编程语言特性&#xff0c;允许在定义函数、类、接口等时使用占位符来表示类型&#xff0c;而不是具体的类型。 泛型是一种在编写可重用、灵活且类型安全的代码时非常有用的功能。 使用泛型的主要目的是为了处…

【Linux】详解如何利用共享内存实现进程间通信

一、共享内存&#xff08;Shared Memory&#xff09;的认识 共享内存&#xff08;Shared Memory&#xff09;是多进程间共享的一部分物理内存。它允许多个进程访问同一块内存空间&#xff0c;从而在不同进程之间共享和传递数据。这种方式常常用于加速进程间的通信&#xff0c;因…

JS打包工具 Vite

Vite是 JS 新一代的打包的工具&#xff0c;它所解决的问题&#xff0c;是前端打包慢的问题&#xff0c;随着前端应用复杂度越来越大&#xff0c;项目文件越来越多&#xff0c;通常项目中都是使用 Webpack 进行打包&#xff0c;Webpack是个静态的打包工具&#xff0c;每次改动都…

9.Jetson AGX Orin protobuf验证

Jetson AGX Orin protobuf验证 前提已经安装好grpc 1&#xff1a;进入目录grpc/examples/cpp/helloworld下编译 make如果出现错误&#xff0c;protoc: Command not found。 进入/usr/local/bin与/usr/local/lib 均没发现protoc与libprotobuf。原来/grpc/third_party/protob…

C语言【指针】

1. 基本语法 1.1 指针变量的定义和使用(重点) 指针是一种数据类型&#xff0c;指针变量指向谁 就把谁的地址赋值给指针变量 1.2 通过指针间接修改变量的值 指针变量指向谁 就把谁的地址赋值给指针变量 可以通过 *指针变量 间接修改变量的值 1.3 const修饰的指针变量 语法…

C语言 【函数】

1.函数概述 函数是一种可重用的代码块&#xff0c;用于执行特定任务或完成特定功能 函数作用&#xff1a;对具备相同逻辑的代码进行封装&#xff0c;提高代码的编写效率&#xff0c;实现对代码的重用 2. 函数的使用 2.1 无参无返回值 #include <stdio.h>// 函数名…

【AAAI2024】点云的自适应邻域提取

论文标题&#xff1a;Point Deformable Network with Enhanced Normal Embedding for Point Cloud Analysis 论文地址&#xff1a;https://ojs.aaai.org/index.php/AAAI/article/view/28497 两个创新点&#xff1a;可变邻域法向量提取 一、由固定邻居变为可变的邻域 二、最小二…

让一个元素在网页上跟随网页窗口大小变化始终保持上下左右居中

废话少说&#xff0c;直接上代码&#xff0c;懂的都懂&#xff1a; <!DOCTYPE html> <html style"font-size: 100px;"> <head><meta http-equiv"Content-Type" content"text/html;charsetUTF-8"><style type"te…

搭建第一个Web服务器(在eclipse或idea上部署Tomcat服务器)

&#x1f4bb;博主现有专栏&#xff1a; C51单片机&#xff08;STC89C516&#xff09;&#xff0c;c语言&#xff0c;c&#xff0c;离散数学&#xff0c;算法设计与分析&#xff0c;数据结构&#xff0c;Python&#xff0c;Java基础&#xff0c;MySQL&#xff0c;linux&#xf…

开关电源测试流程有哪些?如何让测试更简单?

NSAT-8000电源综合测试系统适用于AC-DC、DC-DC电源模块的研发和产线测试&#xff0c;为电源模块测试提供自动化测试方案。用该系统测试开关电源&#xff0c;只需以下操作即可完成&#xff1a; 1. 登录测试系统 2. 在方案运行界面找到已搭建好的开关电源测试方案&#xff0c;点击…

Learn SRP 02

3.Editor Rendering 3.1Drawing Legacy Shaders 因为我们的管线只支持无光照的着色过程&#xff0c;使用其他不同的着色过程的对象是不能被渲染的&#xff0c;他们被标记为不可见。尽管这是正确的&#xff0c;但是它还是隐藏了场景中一些使用错误着色器的对象。所以让我们来渲…

java -spring 图灵 03 各种核心组件

01.BeanDefinition 表示Bean定义&#xff0c;BeanDefinition中存在很多属性用来描述一个Bean的特点。比如&#xff1a; class&#xff0c;表示Bean类型 scope&#xff0c;表示Bean作用域&#xff0c;单例或原型等 lazyInit&#xff1a;表示Bean是否是懒加载 initMethodName&am…

postman 调试 传base64字符串 原来选xml

上个图 工具类 package org.springblade.common.utils;import com.alibaba.fastjson.JSONObject; import org.springblade.modules.tc.mas.Submit;import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStrea…

用示例说明序列化和反序列化

用示例说明序列化和反序列化 序列化和反序列化是将数据结构或对象转换为可存储或传输的格式&#xff0c;以便在需要时重新构建原始数据结构或对象的过程。常见的序列化格式包括 JSON、XML 和 Pickle。 序列化&#xff08;Serialization&#xff09;&#xff1a; 在计算机科学…

嵌入式中C++指针使用方法总结

各位开发者大家好,在分享指针之前,先来看一下int *p[3]和int (*p)[3] 的区别。 int *p[3] p是一个数组,此数组有3个元素,每个元素都是int*类型,也就是指向整型数据的指针类型。 int a=10,b=20,c=30; int*p[3]={&a,&b,&c}; 而int(*p)[3]中的p是一个指向数组的…
最新文章