209.长度最小的子数组(滑动窗口类)

news2024/9/24 13:25:15

文章目录

  • 209.长度最小的子数组
  • 滑动窗口
  • 904. 水果成篮
  • 76. 最小覆盖子串

209.长度最小的子数组

209.长度最小的子数组

给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的 连续 子数组,并返回其长度。如果不存在符合条件的子数组,返回 0

示例:

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

提示:

  • 1 < = t a r g e t < = 1 0 9 1 <= target <= 10^9 1<=target<=109
  • 1 < = n u m s . l e n g t h < = 1 0 5 1 <= nums.length <= 10^5 1<=nums.length<=105
  • 1 < = n u m s [ i ] < = 1 0 5 1 <= nums[i] <= 10^5 1<=nums[i]<=105

思路一:暴力解法
这道题目暴力解法当然是 两个for循环,然后不断的寻找符合条件的子序列,时间复杂度很明显是 O ( n 2 ) O(n^2) O(n2)

Go代码如下:

func minSubArrayLen(s int, nums []int) int {
    n := len(nums)
    if n == 0 {
        return 0
    }
    ans := math.MaxInt32
    for i := 0; i < n; i++ {
        sum := 0
        // 以每个i分别作为各个窗口的起点,找符合条件时的窗口终点j
        for j := i; j < n; j++ { 
            sum += nums[j]
            if sum >= s {
                ans = min(ans, j - i + 1)
                break
            }
        }
    }
    if ans == math.MaxInt32 {
        return 0
    }
    return ans
}

func min(x, y int) int {
    if x < y {
        return x
    }
    return y
}

时间复杂度: O ( n 2 ) O(n^2) O(n2)
空间复杂度: O ( 1 ) O(1) O(1)

滑动窗口

接下来就开始介绍数组操作中另一个重要的方法:滑动窗口

所谓滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果。

在暴力解法中,是一个for循环遍历滑动窗口的起始位置,一个for循环为寻找滑动窗口的终止位置,用两个for循环 完成了一个不断搜索区间的过程。

那么滑动窗口如何用一个for循环来完成这个操作呢。

首先要思考 如果用一个for循环,那么这个for循环移动应该表示 滑动窗口的起始位置,还是终止位置。

如果只用一个for循环来表示 滑动窗口的起始位置,那么如何遍历剩下的终止位置?

此时难免再次陷入 暴力解法的怪圈。

所以 只用一个for循环,那么这个循环的索引,一定是表示 滑动窗口的终止位置。

那么问题来了, 滑动窗口的起始位置如何移动呢?后文解答如果动态调整。

其实不难发现滑动窗口也可以理解为双指针法的一种!只不过这种解法更像是一个窗口的移动,所以叫做滑动窗口更适合一些。

在本题中实现滑动窗口,主要确定如下三点:

  • 窗口内是什么?
  • 如何移动窗口的起始位置?
  • 如何移动窗口的结束位置?

窗口就是 满足其和≥ s的长度最小的 连续 子数组。

窗口的起始位置如何移动:如果当前窗口的值大于等于s了,窗口就要向前移动了(也就是该缩小了)。

窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,也就是for循环里的索引。

解题的关键在于 窗口的起始位置如何移动,如图所示:
在这里插入图片描述
此块代码的精髓就是在不断动态调整窗口的起始位置

for sum >= target && slow < len(nums) {
    // 不断减小的过程中还是可能一直大于等于target的,所以上面是for,而非if
     if (fast - slow + 1) < res {
         res = fast - slow + 1
     }
     sum -= nums[slow]
     slow++
 }
     

可以发现滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将 O ( n 2 ) O(n^2) O(n2)暴力解法降为 O ( n ) O(n) O(n)

Go代码如下:

func minSubArrayLen(target int, nums []int) int {
    //  双指针滑动窗口做法,slow指向当前窗口起点,fast不断后移,达到当前窗口之和大于target时,更新结果
    if len(nums) == 0 {
        return 0
    }

    slow,fast,sum,res := 0,0,0,math.MaxInt32 // 因为要找较小值,所以res先设置一个较大值
    for fast < len(nums) {
        sum += nums[fast]
       
        // 当前窗口已经大于等于target,fast继续后移会一直大于等于了
        // 所以此时应该移动slow,直到小于target为止,而后以最新的slow为起点,找满足条件的新的连续子数组
        for sum >= target && slow < len(nums) {
            // 不断减小的过程中还是可能一直大于等于target的,所以上面是for,而非if
            if (fast - slow + 1) < res {
                res = fast - slow + 1
            }
            sum -= nums[slow]
            slow++
        }
     
        fast++
    }

    // 如果退出上述for时,res还是最大值,即res从未更新过,应该返回0,没有找到任何子数组
    if res == math.MaxInt32 {
        return 0
    }

    return res
}

在这里插入图片描述

时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( 1 ) O(1) O(1)

一些朋友会疑惑为什么时间复杂度是 O ( n ) O(n) O(n)

不要以为for里放一个for就是认为是O(n^2)啊, 主要是看每一个元素被操作的次数,每个元素在滑动窗口中进来操作一次,出去操作一次,每个元素都是被操作两次,所以时间复杂度是 2 × n 也就是O(n)。

904. 水果成篮

904. 水果成篮

你正在探访一家农场,农场从左到右种植了一排果树。这些树用一个整数数组 fruits 表示,其中 fruits[i] 是第 i 棵树上的水果 种类 。

你想要尽可能多地收集水果。然而,农场的主人设定了一些严格的规矩,你必须按照要求采摘水果:

  • 你只有 两个 篮子,并且每个篮子只能装 单一类型 的水果。每个篮子能够装的水果总量没有限制。
  • 你可以选择任意一棵树开始采摘,你必须从 每棵 树(包括开始采摘的树)上 恰好摘一个水果 。采摘的水果应当符合篮子中的水果类型。每采摘一次,你将会向右移动到下一棵树,并继续采摘。
  • 一旦你走到某棵树前,但水果不符合篮子的水果类型,那么就必须停止采摘。

给你一个整数数组 fruits ,返回你可以收集的水果的 最大 数目。

示例 1:

输入:fruits = [1,2,1]
输出:3
解释:可以采摘全部 3 棵树。

示例 2:

输入:fruits = [0,1,2,2]
输出:3
解释:可以采摘 [1,2,2] 这三棵树。
如果从第一棵树开始采摘,则只能采摘 [0,1] 这两棵树。

示例 3:

输入:fruits = [1,2,3,2,2]
输出:4
解释:可以采摘 [2,3,2,2] 这四棵树。
如果从第一棵树开始采摘,则只能采摘 [1,2] 这两棵树。

示例 4:

输入:fruits = [3,3,3,1,2,1,1,2,3,3,4]
输出:5
解释:可以采摘 [1,2,1,1,2] 这五棵树。

提示:

1 < = f r u i t s . l e n g t h < = 1 0 5 1 <= fruits.length <= 10^5 1<=fruits.length<=105
0 < = f r u i t s [ i ] < f r u i t s . l e n g t h 0 <= fruits[i] < fruits.length 0<=fruits[i]<fruits.length

思路
双指针滑动窗口做法:理清下面四个内容,思路就清晰了

  • 窗口内容:只有两种类型的果树,使用哈希表存储这个窗口内的数以及出现的次数。

  • 外层for循环为寻找窗口的终点:我们每次将 right 移动一个位置,并将 fruits[right] 加入哈希表并计数加1

  • 内层也会有个for,不断缩小符合条件的窗口,即窗口的起点后移,寻找以该位置为起点的新的符合条件的窗口。因此内层for循环的作用为判断如果此时哈希表不满足要求(即哈希表中出现超过两个键值对),那么我们需要不断移动 left,将fruits[left] 在哈希表中的计数不断减小,直到为0,并将 fruits[left] 从哈希表中移除。

例如出现[1,2,1,1,2,3,3,4]时,当摘到类型为3的水果时,需要将类型为1的水果都删除,但第二个位置有个类型为2的水果,也要计数减1(Map中对应2的水果类型计数减1,2类型水果比1类型水果后加入Map,所以计数一定会后变为0的,因此符合该轮删除的是1类型水果的诉求)

  • 结果:符合条件的最大窗口长度

Go代码

func totalFruit(fruits []int) (ans int) {
    cnt := map[int]int{}
    left := 0
    for right, x := range fruits {
        cnt[x]++
        for len(cnt) > 2 {
            y := fruits[left]
            cnt[y]--
            if cnt[y] == 0 {
                delete(cnt, y)
            }
            left++
        }
        ans = max(ans, right-left+1)
    }
    return
}

func max(a, b int) int {
    if b > a {
        return b
    }
    return a
}

在这里插入图片描述

76. 最小覆盖子串

76. 最小覆盖子串

给你一个字符串 s 、一个字符串t。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖t所有字符的子串,则返回空字符串 ""

注意:

  • 对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
  • 如果s中存在这样的子串,我们保证它是唯一的答案。

示例 1:

输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"
解释:最小覆盖子串 "BANC" 包含来自字符串 t 的 'A''B''C'

示例 2:

输入:s = "a", t = "a"
输出:"a"
解释:整个字符串 s 是最小覆盖子串。

示例 3:

输入: s = "a", t = "aa"
输出: ""
解释: t 中两个字符 'a' 均应包含在 s 的子串中,
因此没有符合条件的子字符串,返回空字符串。

提示:

  • m == s.length
  • n == t.length
  • 1 <= m, n <= 105
  • s 和 t 由英文字母组成

进阶:你能设计一个在 o(m+n) 时间内解决此问题的算法吗?

思路:
本问题要求我们返回字符串 s 中包含字符串 t 的全部字符的最小窗口。我们称包含t的全部字母的窗口为「可行」窗口。

我们可以用滑动窗口的思想解决这个问题。在滑动窗口类型的问题中都会有两个指针,一个用于「延伸」现有窗口的r指针,和一个用于「收缩」窗口的 l 指针。在任意时刻,只有一个指针运动,而另一个保持静止。我们在s上滑动窗口,通过移动r指针不断扩张窗口。当窗口包含 t 全部所需的字符后,如果能收缩,我们就收缩窗口直到得到最小窗口。

如何判断当前的窗口包含所有 t 所需的字符呢?我们可以用一个哈希表表示 t 中所有的字符以及它们的个数,用一个哈希表动态维护窗口中所有的字符以及它们的个数,如果这个动态表中包含 t 的哈希表中的所有字符,并且对应的个数都不小于t的哈希表中各个字符的个数,那么当前的窗口是「可行」的。

注意:这里 t 中可能出现重复的字符,所以我们要记录字符的个数。

下面是一个好理解的版本,但是在反复的获取Map,提交的时候会出现超时,这里贴出代码,主要是看上去思路比较清晰。

Go代码

func minWindow(s string, t string) string {
    /*
    窗口内容:包含t的所有字符(对于t中重复字符,我们寻找的子字符串中该字符数量必须不少于t中该字符数量。)
    外层for循环:寻找子串的终点
    内层for循环:不断移动,定位到下一个窗口的起点
    结果:最小子串
    */

    if s == "" {
        return ""
    }

    left,right,res,maxLen := 0,0,"",math.MaxInt32
    for right = 0;right <= len(s);right++{
        // 未包含t所有字符时,窗口终点右移
        if !check(s[left:right],getCharsMap(t)) {
            continue
        }

        for left < right {
             
            if check(s[left:right],getCharsMap(t)) {
                if len(s[left:right]) < maxLen {
                res = s[left:right]
                maxLen = len(res)
                }  
                left++ 
            } else{
                break
            }
        }
    }
   
   if maxLen == math.MaxInt32 {
        return ""
   }
    
    return res
}

// 获取所有字符出现的次数
func getCharsMap(s string) map[rune]int{
    res := make(map[rune]int)
    for _,c := range s {
        res[c]++
    }
    return res
}

 // str中是否包含了Map中所有的字符(含重复出现的)
func check(str string,m map[rune]int) bool {
    for _,c := range str {
        if _,ok := m[c];ok { 
          m[c]--
        }
    }

    for _,v := range m {
        if v > 0 {
            // t中的字符还有没被消掉的,说明当前窗口没有包含t的所有字符(重复字符算多个字符)
            return false
        }
    }
    return true
}

在这里插入图片描述

思路中提到的计数版本

Go代码

func minWindow(s string, t string) string {
    ori, cnt := map[byte]int{}, map[byte]int{}
    for i := 0; i < len(t); i++ {
        ori[t[i]]++
    }

    sLen := len(s)
    len := math.MaxInt32
    ansL, ansR := -1, -1

    check := func() bool {
        for k, v := range ori {
            if cnt[k] < v {
                return false
            }
        }
        return true
    }
    for l, r := 0, 0; r < sLen; r++ {
        if r < sLen && ori[s[r]] > 0 {
            cnt[s[r]]++
        }
        for check() && l <= r {
            if (r - l + 1 < len) {
                len = r - l + 1
                ansL, ansR = l, l + len
            }
            if _, ok := ori[s[l]]; ok {
                cnt[s[l]] -= 1
            }
            l++
        }
    }
    if ansL == -1 {
        return ""
    }
    return s[ansL:ansR]
}

在这里插入图片描述

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

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

相关文章

2020ICPC上海 D - Walker M - Gitignore

D: 首先显然要二分,判断当前二分的mid时间下是否能满足走满0~n 枚举所有情况,这里按照左,右起点p1,p2分别讨论 p1向左 p2向左(以下向左和向右都代表向左或者向右到墙,而不代表初速度方向)&#xff0c;只需要计算p1或者p2反弹之后还能走距离n就是合法 p1向左 p2向右&#xff…

C++在Linux实现多线程和多进程的TCP服务器和客户端通信

多进程版本 服务器 #include <arpa/inet.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <sys/wait.h> #include <signal.h> #include <string&…

软件设计师——程序设计语言

目录 低级语言和高级语言 编译程序和解释程序 正规式&#xff0c;词法分析的一个工具 有限自动机 ​编辑 上下文无关法 ​编辑 中后缀表示法 杂题 ​编辑 低级语言和高级语言 编译程序和解释程序 计算机只能理解由0、1序列构成的机器语言&#xff0c;因此高级程序设计…

CAD_Electrical 2022使用记录

一、CAD软件实用调整 1、如何调节窗口背景颜色 例如&#xff1a;将图中白色的背景色调节为黑色。 步骤&#xff1a;在CAD空白区域点击右键 -> 点击选项 -> 在显示中点击颜色(窗口元素) -> 将二维模型空间统一背景的颜色修改成需要的颜色 2、如何调节关标大小 例如&a…

IP纯净度对跨境电商有哪些影响

在全球化贸易的浪潮中&#xff0c;跨境电商凭借其打破地理界限的能力&#xff0c;成为推动国际贸易的重要力量。然而&#xff0c;跨境电商的运营并非没有挑战&#xff0c;其中IP纯净度是影响其成功的关键因素之一。本文将探讨IP纯净度对跨境电商运营的多方面影响&#xff0c;并…

Linux基础---08软件的安装

安装方式优缺点编译安装自由定制&#xff0c;但较为繁琐rmp安装安装简单&#xff0c;但需要自己解决依赖&#xff0c;不支持定制yum安装自动解决rmp依赖&#xff0c;但不支持定制&#xff08;用的更多&#xff09; 下面就具体介绍三大安装方式&#xff1a; 一.编译安装 用Li…

2024/9/15 408“回头看”之应用层小总结(下)

域名系统DNS: 本地域名服务器 本地域名服务器起着代理的作用&#xff0c;会将报文转发到根域名服务器、顶级域名服务器、权限域名服务器。 递归查询&#xff1a; 迭代查询&#xff1a; 文件传送协议FTP: FTP客户和FTP服务器之间使用的是tcp连接。 控制连接使用21端口&…

长业务事务的离线并发问题

事务指代一组操作同时成功或同时失败&#xff0c;事务可分为两类&#xff1a; 系统事务&#xff1a;即关系数据库事务&#xff0c;一次数据库连接中由start transaction或begin开启&#xff0c;commit表示提交&#xff0c;rollback表示回滚&#xff1b;业务事务&#xff1a;完…

海外VS国内:网安上市公司人均创收对比

二级市场分析章节中分析了中国网络网络安全上市公司人均创收63.2万、人均毛利37.6万&#xff0c;人均创利-1.6万。 有网友问了&#xff1a;海外网络安全公司的人均情况如何&#xff1f;那么让我们一起看看吧。 我们统计了在海外上市的28家主要网络安全公司的2023年的人均情况&…

Python互相关统计学 地震学 心理学 数学物理和算法模型及数据科学应用

&#x1f3af;要点 同步时间序列数据地震时频域信息绘制地震噪声干涉图和频谱计算光变曲线和时滞互相关光变曲线并计算峰值和质心图像几何对应关系算法气候相关矩阵图测量麦克风间距离图像相似性量化及显着性统计测试个体同步性量化分析计算绘制有无泊松噪声的光曲线地震幅度和…

通信工程学习:什么是接入网(AN)中的CF核心功能

接入网&#xff08;AN&#xff09;中的CF核心功能 在通信工程中&#xff0c;CF&#xff08;Core Function&#xff09;通常指的是核心功能&#xff0c;它是接入网&#xff08;AN&#xff09;中的一个重要组成部分。CF的主要作用是将用户或业务端口的承载要求与公共传送承载进行…

一键生成中秋国风插画!FLUX中秋专属Lora的使用教程

如何在中秋节期间快速生成富有节日气氛的国风插画吗&#xff1f; 不需要复杂的设计技能&#xff0c;或者手绘功底。只需借助FLUX中秋专属Lora-中秋国风人物插画v1.0_FLUX&#xff0c; 就可以轻松实现一键生成精美插画&#xff0c;特别适合用于宣传海报、包装设计等场景。 这个…

6.接口测试加密接口(Jmeter/工具/函数助手对话框、Beanshell脚本)

一、接口测试加密接口&#xff0c;签名接口 1.加密算法&#xff1a; 可以解密的&#xff1a; 对称式加密&#xff08;私钥加密&#xff09;&#xff1a;AES&#xff0c;DES&#xff0c;Base64 https://www.bejson.com 非对称加密&#xff08;双…

编写程序模版的搭建

1 完整的程序工程有些部分组成 1.1 头文件介绍 头文件嵌套包括的功能 CCS如何找到需要的头文件 1.2 源文件 1.3 库文件 1.4 CMD文件 2 工程模板创建 2.1 获取工程模板基础文件 <

CleanMyMac X 4.15.6正式版 mac直装破解版

你知道 CleanMyMac是什么吗&#xff1f;它的字面意思为“清理我的Mac”&#xff0c;作为软件&#xff0c;那就是一款 Mac清理工具 &#xff0c;Mac OS X 系统下知名系统清理软件&#xff0c;是数以万计的Mac用户的选择。它可以流畅地与系统性能相结合&#xff0c;只需…

dubbo一

Dubbo 分布式系统的演进 SOA面向服务架构 NSA微服务架构 节点与网络 分片和冗余 分布式系统挑战 分布式系统特性与衡量标准 一致性理论 强一致性ACID CAP 弱一致性BASE 一致性算法 dubbo dubbo六大核心功能 API与SPI

【第36章】Spring Cloud之Seata分布式事务

文章目录 前言一、架构图1. 介绍2. 项目结构3. 功能描述 二、用例1. 准备1.1 系统表1.2 业务表1.3 初始化数据 2. 项目搭建2.1 项目结构2.2 主要依赖2.3 主要配置 三、主要业务代码1. 仓储服务1.1 controller1.2 service1.3 dao 2. 订单服务1.1 controller1.2 service1.3 dao 3…

对操作系统(OS)管理和进程的理解

文章目录 从冯诺依曼体系入手来了解计算机硬件部分操作系统操作系统的概念设计操作系统&#xff08;OS&#xff09;的目的对下&#xff08;硬件&#xff09;OS的管理对上如何理解系统调用 进程 在计算机系统中&#xff0c;硬件、操作系统和进程是三个至关重要的概念。它们相互协…

【数据库】MySQL内置函数

本篇分享一些在MySQL中常见的一些内置函数&#xff0c;如日期函数&#xff0c;字符串函数和数学函数&#xff0c;以方便于操作数据库中的数据。 1.日期函数 我们先整体观察一下这些函数再讲解案例 日期函数使用起来都非常就简单 获得年月日&#xff1a; select current_dat…

LocalMamba: Visual State Space Model with Windowed Selective Scan 论文总结

题目&#xff1a;LocalMamba: Visual State Space Model&#xff08;视觉状态空间模型&#xff09; with Windowed Selective Scan&#xff08;窗口化的选择扫描&#xff09; 论文&#xff1a;[2403.09338] LocalMamba: Visual State Space Model with Windowed Selective Scan…