数组--part 4--长度最小的子数组(力扣299/904/76)

news2024/11/26 21:34:14

文章目录

    • 算法基本思想
    • leetcode 209 长度最小的子数组
    • leetcode 904 水果成篮
    • leetcode 76 最小覆盖子串

算法基本思想

首先对于滑动窗口,题目可以先去看看leetcode 209 进行相关的了解后,再来书写代码。

首先我们的第一想法肯定就是暴力解法:也就是采用两层循环进行遍历,寻找相关的运算并得到最后的答案。下面是暴力的解法:

int minSubArrayLen(int s, vector<int>& nums) {
        int result = INT32_MAX; // int的最大值
        int sum = 0; // 子序列的数值之和
        int subLength = 0; // 子序列的长度
        for (int i = 0; i < nums.size(); i++) { // 设置子序列起点为i
            sum = 0;
            for (int j = i; j < nums.size(); j++) { // 设置子序列终止位置为j
                sum += nums[j];
                if (sum >= s) { // 一旦发现子序列和超过了s,更新result
                    subLength = j - i + 1; // 取子序列的长度
                    result = result < subLength ? result : subLength;
                    break; // 因为我们是找符合条件最短的子序列,所以一旦符合条件就break
                }
            }
        }
        // 如果result没有被赋值的话,就返回0,说明没有符合条件的子序列
        return result == INT32_MAX ? 0 : result;
    }

而对于这个代码进行分析的话,我们会发现他的时间复杂度是O(n2),实际上对于这个量级的数据,我们这种算法实际上已经需要更新的了,很容易超时。

那有没有一种方法可以有效的减少时间复杂度呢?回忆一下之前part2部分的内容,我们实际上是需要两层for循环来遍历数组的事情,我们采用双指针法来实现得到了一层循环做到了两层循环做到的事情,故双指针法是一条道路。

双指针法的实现过程:

int function1(int target, vector<int>& nums) {
	//首先我们先可以看到这里,我们决定采用双指针法,来仅仅采用一次循环达到题目所需要的要求。
	//先定义具体的指针。sum的值实际上就是数组从begin开始加到end为止
	int begin = 0, end = 0;
	int sum = 0;
	//对于我们来说,我们第一步的想法肯定是,遍历begin然后调整end来进行控制中间数据的叠加,但是当你写下begin++开始变化的时候
	//你会发现,后面的end随之变化,我们最后写出来的代码实际上也就是跟两层for循环一样,实际上也就是和暴力算法没有区别。
	for ( begin = 0; begin < nums.size(); begin++)
	{
		//定义sum 并且变化end
		
		//for 循环从begin相加到end
	}
}

上面的function的方式,我们似乎没有逃开两层循环的限制。所以我们来分析一下,为什么开始begin不能等于0,实际上我们想要让整个函数的运行仅仅只有一层for循环,我们所需要做的事情就是,随着循环的变化,begin和end仅仅只有一个自由度的变化。所以我们需要采用end的变化,来进行操作,看不懂的可以看看代码,然后理解一下过程。

int function1(int target, vector<int>& nums) {
	//首先我们先可以看到这里,我们决定采用双指针法,来仅仅采用一次循环达到题目所需要的要求。
	//先定义具体的指针。sum的值实际上就是数组从begin开始加到end为止
	//current_len统计的是从begin到end之间的数据有多少个
	//result返回最后的最短窗口 初始最大的int数,方便操作
	int begin = 0, end = 0;
	int sum = 0;
	int current_len = 0, result = INT32_MAX;
	//采用end++的变化
	for ( end = 0; end < nums.size(); end++)
	{
		sum += nums[end];
		//这里if注意一下,我们先按照自己感性的想法来写
		if (sum >= target)
		{
			//当中间的数字大了,我们实际上就可以统计窗口的大小,并且将begin进一位,然后再次统计了。
			current_len = end - begin + 1;
			//选择小的窗口大小
			result = result < current_len ? result : current_len;
			//准备下一次的循环,我们发现现在的数据已经大于target了,所以我们需要减掉begin的值,将左指针向右移动,然后后续右指针再进行移动窗口,得到最后的值
			sum -= nums[begin++];
		}
	}
	//最后返回数组的大小,也就是result,但是不要忘记存在一个数组全部加起来都没有大于target,此时result的值为INT32_MAX
	return result == INT32_MAX ? 0 : result;
}

但是这样子又有什么问题呢?这也是初学者最容易犯错的地方。
给一个样例,如果nums[1,1,1,1,1,100] target = 100的话会发生什么

我们用上面的代码进行运行,会发现在那个if的地方会存在问题,最开始begin = 0,end开始循环达到5的时候 sum才大于100,然后进行一系列计算 此时result = 6 sum = 104 ,问题就在sum上,sum此时还是大于100的数据,但是我们是if,直接进行后面的(end++)了,而此时sum是有问题的,因为我们此时应当做的事情是继续移动左边的begin缩小直到sum小于100,这样子后面移动end,才是有效的,所以那个地方应该采用while。

int function1(int target, vector<int>& nums) {
	//首先我们先可以看到这里,我们决定采用双指针法,来仅仅采用一次循环达到题目所需要的要求。
	//先定义具体的指针。sum的值实际上就是数组从begin开始加到end为止
	//current_len统计的是从begin到end之间的数据有多少个
	//result返回最后的最短窗口 初始最大的int数,方便操作
	int begin = 0, end = 0;
	int sum = 0;
	int current_len = 0, result = INT32_MAX;
	//采用end++的变化
	for ( end = 0; end < nums.size(); end++)
	{
		sum += nums[end];
		//这里if注意一下,我们先按照自己感性的想法来写
		while (sum >= target)
		{
			//当中间的数字大了,我们实际上就可以统计窗口的大小,并且将begin进一位,然后再次统计了。
			current_len = end - begin + 1;
			//选择小的窗口大小
			result = result < current_len ? result : current_len;
			//准备下一次的循环,我们发现现在的数据已经大于target了,所以我们需要减掉begin的值,将左指针向右移动,然后后续右指针再进行移动窗口,得到最后的值
			sum -= nums[begin++];
		}
	}
	//最后返回数组的大小,也就是result,但是不要忘记存在一个数组全部加起来都没有大于target,此时result的值为INT32_MAX
	return result == INT32_MAX ? 0 : result;
}

但是你会发现,此时又存在一个情况,也就是当如果begin的移动,让begin == end了呢?此时会发生什么情况,自己分析一下,也有助于理解这个过程,不行可以debug一下,这边只进行结论介绍,此时没有什么问题,也是可以继续使用,没有错。但我们这么想,当二者相等的时候如果还依然大于的时候,其实我们已经可以直接进行return 1节省步骤了,所以在此处可以进行小小的“优化”?。

这里为什么打 ? 呢,先按下不表,实际上如果在循环中加上一个判断语句,进行的优化,实际上是负面优化。

leetcode 209 长度最小的子数组

链接

基础见上面的题目,进行书写:

AC-code

class Solution {
public:
    int minSubArrayLen(int s, vector<int>& nums) {
        int result = INT32_MAX;
        int sum = 0; // 滑动窗口数值之和
        int i = 0; // 滑动窗口起始位置
        int subLength = 0; // 滑动窗口的长度
        for (int j = 0; j < nums.size(); j++) {
            sum += nums[j];
            // 注意这里使用while,每次更新 i(起始位置),并不断比较子序列是否符合条件
            while (sum >= s) {
                subLength = (j - i + 1); // 取子序列的长度
                result = result < subLength ? result : subLength;
                sum -= nums[i++]; // 这里体现出滑动窗口的精髓之处,不断变更i(子序列的起始位置)
            }
        }
        // 如果result没有被赋值的话,就返回0,说明没有符合条件的子序列
        return result == INT32_MAX ? 0 : result;
    }
};

leetcode 904 水果成篮

链接

class Solution {
public:
    int totalFruit(vector<int>& fruits) {
        //采水果的滑动窗口,小暴力算法
        int begin = 0, end = 0;
        int current_len = 0, max_len = 0;
        int tag = 0, flag = 0;//用于标记 先按下不表
        //定义两个篮子
        vector<int >basket(2, -1);
        for (end = 0; end < fruits.size(); end++)
        {
            //第一个篮子没装
            if (basket[0] == -1)
            {
                current_len = 1;
                max_len = max_len > current_len ? max_len : current_len;
                basket[0] = fruits[end];
                continue;
            }
            //第二个篮子没装,且出现新的类型水果,显然是下面的这段代码块
            //第二个篮子没装,而没有出现新的类型的水果,此时我们需要做什么?
            //想一下,发现我们仅仅需要让current_len(水果种类长度大小加一)。
            //但是又想了一下,其实最后面统计current_len其实只需要通过end和begin的相加减,即可。故不需要额外定义
            //不然此处的代码应当是:
            //eles if(basket[1] == -1 )
            //{ 
            //  if(basket[0] == fruits[end])
            //  {内部代码}
            //  else{内部代码
            //      }
            //  }
            else if(basket[1] == -1 && basket[0] != fruits[end])
            {
                current_len += 1;
                max_len = max_len > current_len ? max_len : current_len;
                basket[1] = fruits[end];
                continue;
            }
            //当篮子都确定了,此时出现水果 有两种情况
            //第一种:水果出现过 其实实际上就是需要current_len++,同理于上,但是这边为什么要写,分析过程见下
            //第二种:水果没有出现过,停止,统计总长度,并且重新决定begin的开始,故采用while
            else if(basket[0] != -1 && basket[1] != -1 && basket[0] != fruits[end] && basket[1] != fruits[end])
            {
                current_len = end - begin;//注意没有减一,此时的end指向的是下一个元素.
                max_len = max_len > current_len ? max_len : current_len;
                //修改begin 值得关注的是,我们需要去判断begin的位置
                //先按照给出的例子来总结规律
                //第一个 0 1 2 2   第一次是0 1 begin要变成1,去掉0
                //第二个 1 2 3 2 2 第一次是1 2 begin要变成1,去掉1
                //比较奇怪的选项有1 2 1 3 就是夹着的,所以我们此处做的选择需要同时把 1 和 2删除,所以需要做的事情是从前往后走,找到第一个1之后的数据,为什么是这样子的,希望读者自己弄明白,我也在最后在申明
                //而为了不浪费for循环的时间,所以我们将tag写在了下方,实际上这个是后续加的,希望读者明白这个分析过程
                basket[0] = fruits[begin == tag ? tag + 1 : tag];
                begin = begin == tag ? tag + 1 : tag;
                tag = begin;
                //但是此时我们又发现存在了问题,此时对于第二个篮子我们该怎么定义,这里实际上也可以分类讨论,但证明到这里,我们确实发现存粹的算法已经不适合了,hh也可以继续
                //这边取个懒 归空, end从新开始
                basket[1] = -1;
                end = begin;
                current_len = 1;
                flag = 0;
                continue;
            }
            else
            {
                current_len = end - begin + 1;//注意,此时的end指向.
                max_len = max_len > current_len ? max_len : current_len;
            }
            //下面的情况是第一种,也就是三个篮子,但出现同种类型的水果
            //剩下的情况就是需要更新tag用于表示实时等于begin的元素的位置
            if (!flag && fruits[end] == basket[0])
            {
                flag = 1;
                tag = end;
            }
        }
        return max_len;
    }
};

但是即使是这样子都是无法通过的,原因就在于不够简化,实际上内部数据存储了一个这样子的检验数:
在这里插入图片描述
阿巴阿巴,所以我们需要一种新的想法来帮助我们进行实现上述的代码.其实不会告诉你,改起来很麻烦,笔者也改不动的.

引入STL思想: 实际上生活中很多东西如果采用STL来写的话,实际上都会简单很多,但是既然选择刷题,这些东西就需要自己去实现,但也并不意味着脱离STL,个人观点还是,如果对于一道题中,你是否调用STL对于算法直接产生了很大的影响,那建议还是自己进行完成,如果在调用的过程中,只是利用了其特性,或者对于时间复杂度没有太大的影响,那就可以采用STL.

实际上就是加上一个哈希map,利用哈希map的特性进行缩减变化.

class Solution {
public:
    int totalFruit(vector<int>& fruits) {
        //定义字典集,前一个int代表水果类型,后一个int代表数量
        unordered_map<int, int >dic;
        int begin = 0, end = 0;
        int max_len = 0;
        for ( end = 0; end < fruits.size(); end++)
        {
            //利用int的默认初始化值为0,以及map对于一个没有出现在容器当中的元素,进行[]选取,实际上完成了初始化的过程的两个细节.
            ++dic[fruits[end]];
            //经典while再现 当出现了两个字典的索引,代表出现了两种以上的水果
            while (dic.size() > 2)
            {
                //返回迭代器 后续通过迭代器进行修改内部的值
                auto iter = dic.find(fruits[begin]);
                --iter->second;
                if (iter->second == 0)
                {
                    dic.erase(iter);
                }
                begin++;
            }
            max_len = max(max_len, end - begin + 1);
        }
        return max_len;
    }
};

上头,这题有点难啃,建议读者做到这道题,可以几天内多看几遍,理解一下思路.

总结(后话):

此题一开始关于是采用模拟,还是采用分类的想法选择上,决定了两种不同的算法思想,关于分类,就是文中的第一段代码,明显复杂了很多,也需要考虑很多东西,而关于模拟的话,实际上采用stl 才简便,不然也是极度复杂的.

leetcode 76 最小覆盖子串

链接

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

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

相关文章

html实现汉诺塔小游戏

文章目录 1.设计来源汉诺塔由来1.1 主界面1.2 游戏规则1.3 游戏完成界面 2.效果和源码2.1 动态效果2.2 源代码 源码下载 作者&#xff1a;xcLeigh 文章地址&#xff1a;https://blog.csdn.net/weixin_43151418/article/details/130606736 html实现汉诺塔小游戏源码 汉诺塔&…

数据结构-链表(详解)

前言&#xff1a;内容包括&#xff1a;链表的分类&#xff0c;无头单向非循环链表的增删查改的实现&#xff0c;带头双向循环链表的增删查改的实现 目录 链表的分类 1. 单向或者双向 ​编辑 2. 带头或者不带头 3. 循环或者非循环 无头单向非循环链表&#xff1a; ​编辑…

造梦日记 Printidea 用户手册

*Hi 造梦日记* 欢迎来到造梦日记的空间&#xff5e; 造梦日记Printidea是一款基于AI算法、输入文字或图片即可生成高质量图片的工具&#xff0c;由西湖大学深度学习实验室和西湖心辰联合出品&#xff0c;超强算力&#xff0c;0.8秒出图&#xff0c;目前支持微信小程序、网页端…

边缘计算盒子的特点?边缘计算盒子适用于什么场景

边缘计算盒子&#xff08;Edge Computing Box&#xff09;是一种用于边缘计算的硬件设备&#xff0c;它通常是一个小型的计算设备&#xff0c;具备一定的计算能力和存储资源&#xff0c;并且能够连接到网络。边缘计算盒子的主要目的是在物联网&#xff08;IoT&#xff09;和分布…

续ShaderEditor、Inspector之后又一成功爆品,2周260+单!

01 前言 大家好&#xff0c;我是98K&#xff01;五一前&#xff0c;我上架 Cocos Store 的『高性能割草框架』增加 Cocos Creator 2.4.x 引擎&#xff0c;已经支持的老铁可免费更新&#xff01; 后续的更新计划是&#xff1a;完善2D游戏案例、增加3D案例、支持RVO和群聚&#…

Android WebView 长按弹出的文本选择器如何监听滑动和如何弹出完全自定义的菜单栏

在这次改版中&#xff0c;h5小伙伴与我沟通说要把长按选择改用成原生的拉选框&#xff0c;之前我也没搞过呀&#xff0c;开始研究吧。 怎么研究呀&#xff0c;当然是百度一下了。 百度了一天总结如下&#xff1a; 好多文章都是告诉你如何在系统的基础上来修改自己的文字和点…

vue2 框架运行原理剖析系列(二)之 组件挂载$mount神秘之旅!!!

一、vue组件挂载 1.1 上一篇文章中&#xff0c;介绍到组件执行 mountComponent 函数&#xff0c;本文对此展开详细的讲解。 1.2 调用改方法的位置在于entry-runtime-with-compiler.js 的Vue.prototype.$mount&#xff0c;具体代码如下&#xff1a; 其中&#xff0c; &#xff…

【图像融合】Dif-Fusion:基于扩散模型的红外/可见图像融合方法

文章目录 摘要一、前言二、相关工作1.红外线和可见光的图像融合2.扩散模型&#xff08;可见博主之前的博客&#xff09; 三、方法1.红外线和可见光图像的联合扩散2*.多通道扩散特征的融合 四、实验1.实验设置2.融合性能分析&#xff08;效果展示&#xff09;3.泛化实验 总结 摘…

通知短信 API 技术细节以及发送流程机制原理解析

引言 短信是一种简单、直接、高效的通信方式&#xff0c;被广泛应用于各个领域。在移动互联网时代&#xff0c;短信成为了客户服务、政府通知、公共服务等方面的重要工具。为了更好地利用短信这种通信方式&#xff0c;通知短信 API应运而生。短信API可以帮助企业、政府和应用程…

RK3588旗舰32T人工智能多网口边缘智能网关交换机

32T边缘智能网关发布&#xff0c;助力多行业数字化升级&#xff0c;运维降本增效&#xff0c;搭载RK3588旗舰芯 搭载瑞芯微RK3588芯片的边缘智能网关XM-RK3588&#xff0c;算力可扩展至32T&#xff0c;适用于电力能源、智慧交通、智慧城市、智慧安防、智慧医疗、工业互联网等领…

前端的加密和解密,crypto-js的应用,AES / RSA / md5

每日鸡汤&#xff1a;每个你想学习的瞬间&#xff0c;都是未来你的向自己求救 内容预警*****新手内容&#xff0c;自己学习总结用****大佬请绕道 之前看https原理&#xff0c;看到对称加密和非对称加密&#xff0c;各种加密方法&#xff0c;看得云里雾里&#xff0c;即便是总结…

报错main.py: error: unrecognized arguments: stack_size 4 1001,770,123

运行从GitHub上面下载下来的代码时&#xff0c;按照作者提供的输入命令输入后报错&#xff1a; main.py: error: unrecognized arguments: stack_size 4 1001,770,123 将报错的部分在网上百度&#xff0c;找到部分方法&#xff0c;得出理解&#xff1a;输入的命令是出错的&am…

【RS专题】怎么知道你遇到的是rs风控

本文属于技术分享、如有侵权可联系本人下架 最简单的方法就是查看cookie,在控制台输入【document.cookie】 如果出现如上图中有【xxxxxxT】或者【xxxxxxP】的,并且它的值都为英文数字和下滑线加点,那么基本可以确定这个网站用了rs反爬 什么是rs反爬,下面抄一段内容 瑞数动…

【瑞数RS专题】首层代码分析,和获取eval层代码,cookie反爬虫详解

如有侵权、联系本人下架 以下面两个网站为例 1.aHR0cDovL3d3dy5mYW5nZGkuY29tLmNuL25ld19ob3VzZS9uZXdfaG91c2VfZGV0YWlsLmh0bWw= 2.aHR0cHM6Ly93d3cubm1wYS5nb3YuY24veWFvd2VuL3lwamd5dy9pbmRleC5odG1s 首先明确一下目标,我们要先获取网页200的源代码,RS5代第一次响应为…

unittest自动化测试框架讲解以及实战

为什么要学习unittest 按照测试阶段来划分&#xff0c;可以将测试分为单元测试、集成测试、系统测试和验收测试。单元测试是指对软件中的最小可测试单元在与程序其他部分相隔离的情况下进行检查和验证的工作&#xff0c;通常指函数或者类&#xff0c;一般是开发完成的。 单元…

软件架构:软件架构设计的三个维度

架构设计是一个非常大的话题&#xff0c;不管写几篇文章&#xff0c;接触到的始终只是冰山一角&#xff0c;更多的是实践中去体会。这篇文章主要介绍面向对象OO、面向方面AOP和面向服务SOA这三个要素在架构设计中的位置与作用。   架构设计有三个维度&#xff0c;或者说是我们…

文案改写神器软件-文案改编神器

文案改写神器软件 文案改写神器软件通常致力于通过人工智能技术将一篇已有文案进行改写和改编&#xff0c;以达到复用或优化的目的。以下是文案改写神器软件通常可以做的事情&#xff1a; 改写原文&#xff1a;文案改写神器可以通过自定义规则、语法分析和文本相似性匹配等功能…

设计模式之【组合模式】,树形结构的完美解决方案

文章目录 一、什么是组合模式1、组合模式三大角色2、组合模式应用场景3、组合模式注意事项和细节 二、透明组合模式1、学院院系案例2、透明组合模式总结 三、安全组合模式1、linux目录系统案例2、安全组合模式总结 四、源码中使用的组合模式1、HashMap2、ArrayList3、MyBatis 一…

跨平台应用开发进阶(六十一):uni-app 跨平台技术开发框架可行性调研

文章目录 一、流程类二、研发类(uni-app框架)三、心得分享(研发踩坑&uni-app踩坑)四、拓展阅读 一、流程类 IOS和安卓企业开发者账户申请流程(申请渠道、需要提供的相关证明、审核时间等)。 答&#xff1a;uni-app使用HBuliderX作为开发IDE,支持邮箱、密码方式注册&#x…

95、Image Restoration with Mean-Reverting Stochastic Differential Equations

简介 主页&#xff1a;https://github.com/Algolzw/image-restoration-sde 扩散模型终于在去噪、超分辨率等应用了。 这是一种基于随机微分方程的通用图像恢复方法&#xff0c;关键结构包括均值还原SDE&#xff0c;该SDE将高质量图像转换为具有固定高斯噪声的平均状态的降级…