[Lc滑动窗口_1] 长度最小的数组 | 无重复字符的最长子串 | 最大连续1的个数 III | 将 x 减到 0 的最小操作数

目录

1. 长度最小的字数组

题解

代码

⭕2.无重复字符的最长子串

题解

代码

3.最大连续1的个数 III

题解

代码

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

题解

代码


1. 长度最小的字数组

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

题目分析:

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

找出该数组中满足其总和大于等于 target 的长度最小的 子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度如果不存在符合条件的子数组,返回 0

子数组:是连续的!!!)

示例 1:

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

示例 2:

输入:target = 4, nums = [1,4,4]
输出:1

题解

  • 注意题目说的是 正整数 数组,说明数组里面的数是大于等于0的数。
  • 因此这道题我们有一种优化的方法。
  • 题目让找连续的子数组和>=target,并且长度最小。有很多种情况,但是我们选择的是 最小长度。

算法原理:

不管什么题,首先我们一定会先想到的是 暴力求解,因为只有暴力求解出来了,我们就可以在暴力求解的基础上进行优化~

解法一:暴力枚举出所有的子数组的和

两层for循环,O(N^2)

for(int i=0;i<n;i++)
    for(int j=i;j<n;j++)
        sum+=num[j];
//固定一个数,挨个往后加
        if(sum>=target)
            min(len,j-i+1)

注意到此时暴力枚举是有优化的。

题目说的是一个 正整数数组,都是大于等于0的数,这个 sum是呈现出递增的状态的,单调递增!

在暴力求解中,此时right还要++,但是注意题目本来要求的就是 最小长度

此时sum在加上往上走了一步的right的num[right],一定是满足sum>=target,但是len变成5了,一定不会是最终结果

因此当条件已经满足sum>=target ,right就不用动了。right后面也就不用再枚举了。

那现在让 left+1,right和left指向同一下标,然后再重复上面过程,那有个问题,这段区间的和能不能直接算出来?

  • 当然可以。
  • 现在sum=8,我只需要把让sum减去num[left],不就是现在left和right所在的区间和算出来吗。
  • 没有必要让right傻傻的回退然后重新加。因此right不动,更新sum=6.

因此我们从暴力枚举中发现两个优化:

  • 一个是right 满足后,后面不用枚举
  • 一个left++,right不用回退,

所以我们可以利用单调性,使用双指针优化。


解法二:利用单调性,使用 “同向双指针来优化

当我们在暴力枚举的策略中发现left和right都是从左向右一个方向移动,我们就称为这两个指针叫做同向指针。同向双指针又称为滑动窗口。

什么是滑动窗口?

本质上是 “同向双指针”,left从左到右移动,right不回退,从左到右移动,用left和right一直 维护这个区间的和,然后这两个指针从左向右移动的过程非常像一个窗口在这个数组里滑来滑去。

什么时候用滑动窗口?

利用单调性,用滑动窗口解决问题。

当我们发现在暴力求解时,两个指针都可以做到 不回退,都是向同一个方向移动的时候,此时就可以用滑动窗口。

滑动窗口怎么用?

  1. 初始left=0,right=0,充当窗口左端点,右端点。用left,right标记窗口左区间,右区间。
  2. 右窗口(++right)(右值进窗口)
  3. 判断
    • 根据判断决定是否 左窗口(++left)(左值出窗口)
  1. 更新结果
    • 2,3都有可能会更新结果,看题目要求

左窗口,判断,右窗口一直循环,直到right 超过区间长度结束更新结果看题目要求(右窗口,左窗口都有可能),。

滑动窗口正确性

  • 暴力枚举肯定对的,因为已经把所有子数组的情况都找出来了。
  • 虽然滑动窗口并没有把没有把所有情况都枚举出来,但是这里利用单调性,规避了没有必要的枚举
  • 虽然没有把所有情况真正枚举出来,但是已经判断出有些子数组不是最终结果,已经把所有结果都考虑进来了,所以这种策略是跟暴力枚举是一样正确的。

滑动窗口时间复杂度

进窗口是一个循环,判断也是一个循环。

两层循环套在一起。可能会觉得时间复杂度O(N^2),但是不能看代码算时间复杂度,要看实际情况分析实际复杂度。

实际我们只会让right向前移动,left也向前移动,即使时最坏情况,right移动到最后一个元素,left 也移动到最后一个元素,因为单调性,总共也就操作了 n+n=2n 次 整体时间复杂度O(N)


代码

要考虑到,栈溢出(heap-buffer-overflow) 的边界情况

可详见前文

在Leetcode中,无穷大和无穷小分别怎么表示

C/C++中可以使用INT_MAX和INT_MIN


⭕2.无重复字符的最长子串

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

题目分析:

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

示例 1:

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

示例 2:

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

示例 3:

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

子串和子数组都是连续的

❗❗❗ 模拟分析示例:

题解

算法原理:

首先还是暴力枚举,然后根据暴力枚举进行优化。

  • 以下面为例,两层for循环,但是下面找到的结果都是我们站在上帝角度,编译器并不知到什么时候结束。
  • 一般对应判断是否有重复元素,我们都可以用哈希表来解决问题。
  • 使用哈希表,判断是否有重复元素,比如让你判断一个数组是否有重复,或者两个数组是否有重复都可以用哈希映射!

解法一:暴力枚举+哈希表(判断字符是否重复出现)

O(N^2)

根据解法一做优化,定义一个left,right指针。当right走到有重复的元素后,已经找到一个字串,其中left到right区间每个元素都已经进入hash表。

此时left向前走一步,但是这个区间还是有重复元素,因此left要走到没有重复的区间才行,

然后这个时候以前做法是right回退然后重新往下走,但是这里left到right区间元素本来就在hash表里

因此就不需要right回退了,而是向right继续向前走。然后重复上面过程,直到right走到结尾。结束~

这不就是滑动窗口的思想吗。双向指针,left往前走,right不回退一直往前走

解法二:利用规律,使用 “滑动窗口” 解决问题

  1. left=0,right=0
  2. 进窗口
  3. 判断
    • 出窗口
  1. 更新结果

进窗口、判断、出窗口,更新结果是一个大循环过程。直到right到结尾循环结束。

其中判断、出窗口是一个小循环(直到跳出重复字符)。不过时间复杂度还是O(N).

注意:

  • 更新结果可能在  进窗口后,判断后,出窗口后,判断后任意一个地方,看题目要求

本题:

  • 进窗口 ->-> 让字符进入哈希表
  • 判断-> 窗口内出现重复元素
  • 出窗口-> 从哈希表中删除该字符

代码

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        //!!
        if(s.empty()) return 0; // 处理空输入

        vector<char> str;
        for(char c:s) str.push_back(c);

        int left=0,right=0,n=str.size(),len=0;
        //unordered_set ret;
        unordered_set<char> ret;

        while(right<n)
        {
//先检查
            while(ret.count(str[right]))
            {
                ret.erase(str[left]);
                left++;
                //利用了连续性
                //表中 发现了右元素已存在
                //要在左边 进行跳过
            }
//不存在 就插入
            ret.insert(str[right]);
            len=max(len,right-left+1);
            right++;
        }
        return len;
    }
};

总结一下:

利用单调性,使用 双指针 解决问题。

  • 一般left和right,一个指向数组最左边,一个指向数组最右边,然后一次可以排除一批,再然后left++,–right,两个指针是对撞的。

这里利用单调性或者利用规律,使用 滑动窗口 解决问题

  • 滑动窗口也使用双指针解决问题,不过left一直从前往后走,right不回退从前往后走,两个指针是同向的。因此滑动窗口本质其实是 同向双指针。

3.最大连续1的个数 III

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

题目分析:

给定一个二进制数组 nums 和一个整数 k,假设最多可以翻转 k0 ,则返回执行操作后 数组中连续 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。

题解

题目说的翻转实际上是把0变成1的意思,最多翻转K次,说明小于等于K都是可以的。

拿到题我们开始肯定想的是暴力求解。

如果直接暴力求解,遇到0->1了,那下一次在遍历就有问题了。

因此我们换一个思路。这道题不是让转化后最大连续1的个数吗。

我们转化为:找出最长的子数组,数组里0的个数不超过K个,这个数组里面0一定能够转化成1。

算法原理:

解法一:暴力枚举+zero计数器

伪代码,两层for循环,统计zero的个数,满足zero>k,统计此时数组长度,然后重新进入循环,注意每次zero都清0

for(int i=0;i<n;++i)
    for(int j=i;j<n;++j)//双指针 查找出一段区间
        if(nums[j]==0)
            ++zero;
        if(zero>k)
            ret=max(ret,right-left+1)

然后我们根据暴力枚举,看看有没有优化的可能。

定义两个指针left,right,right走到zero>k的位置,zero=3,大于k。

按照暴力求解left++,然后right回溯然后重新往后走。但是我们发现没有必要,现在left往前走一步,你会发现,right还是停留在老位置,这个区间不用在管的,直接丢弃。

因此,让left一直走到zero<=k的位置。然后 right也根本不用回溯 然后在重新走,而是直接往后走就行了。

根据上面的发现,当在暴力枚举中,发现left,right是同向移动的,利用这个规律,使用滑动窗口解决问题

解法二:利用规律,使用滑动窗口

  1. left=0,right=0
  2. 进窗口
  3. 判断
    • 出窗口
  1. 更新结果

进窗口 -> 如果是1,不理会。如果是0,计数器+1

判断 -> zero>k

出窗口 -> 如果是1,不理会。如果是0,计数器-1

更新结果:在判断之后在更新

代码

class Solution {
public:
    int longestOnes(vector<int>& nums, int k) {
        int left = 0, right = 0, zero = 0;
        int n = nums.size(), len = 0;

        while (right < n) {//进 窗口
            if (nums[right] == 0)
                zero++;
            while (zero > k) {//循环判断
                if (nums[left] == 0)
                    zero--;
                left++;
            }
            len = max(len, right - left + 1);
            right++;
        }
        return len;
    }
};

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

题目链接:1658. 将 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 。

题解

这道题让每次从数组左右两边移除一个数,然后就是一个新的数组,然后再从新的数组再从左右两边移除一个数。

  • 但是如果真的硬着头皮开始做,其实是很困难的。
  • 并不知道每次是从最左边走还是最右边找。有可能这次左边下次右边或者还是左边,情况太复杂了。

因此我们可以利用 正难则反 的思想

  • 正对面解题太难,那就想对立面,换个思路。
  • 不是每次从左右两端找一个数吗,那可能找到情况就是a+b=x,a、b什么情况都要,但是中间这个连续区间的和不也是确定的吗sum-x
  • 也就是这道题我们转换成,找出最长的子数组长度,所有元素的和正好等于sum-x,然后数组总长减去这段子区间长度不就是问题答案吗
  • 如果没找到说明这个数组不存在将x减到0的数,直接返回-1

解法一:暴力求解

初始left,right指向同一下标,当right走到和大于target的时候,left往前走

按照暴力求解,right要回到和left相同下标,然后right在重新往前走,直到再次走到和大于target的地方停下来,然后重复上面过程。

  • 但是今天这里不需要right回溯,因为right回溯后重新走到下面的位置,因为left已经往前走了,这段区间的和肯定是更小了
  • 因此就不需要right回溯了。要么right不动,要么right往后走。
  • 同向双指针 ----> 本质就是滑动窗口

解法二:使用滑动窗口

代码


class Solution {
public:
int minOperations(vector<int>& nums, int x)
{
    if(nums.empty()) return -1;

    int sum=0;
    for(auto c:nums)
        sum+=c;

    // 新增边界条件处理
    if (sum == x) return nums.size();  // 整个数组和正好等于x
    if (sum < x) return -1;            // 总和不足,无法达成目标

    int target=sum-x,len=0;
    int left=0,right=0,n=nums.size(),add=0;

    while(right<n)
        {
            add+=nums[right];
            while(add>target)
                {
                    add-=nums[left];
                    left++;
                }

            if(add==target)
                len=max(len,right-left+1);

            right++;
        }

    //!!!len>0      
    return (len > 0) ? (nums.size() - len) : -1;
}
};

测试样例跑不全时,要注意对 边界情况 的处理

  • 若不存在这样的子数组(len = 0

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

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

相关文章

数据库Redis数据库

目录 一、数据库类型 1、关系型数据库 2、非关系型数据库 3、关系型非关系型区别 二、Redis数据库 1、什么是Redis 3、Redis特点 4、Redis为什么读写快 5、部署Redis数据库 6、redis管理 7、Redis数据库五大类型 8、Redis数据库基础使用 9、redis五大类型增删查?…

蓝桥备赛(四)- 数组(下)

一 、 字符数组 1.1 介绍 数组的元素如果是字符类型 &#xff0c; 这种数组就是字符数组 &#xff0c; 字符数组可以是一维数组 &#xff0c; 可以是二维数组 (多维数组)。 接下来主要讨论一维的字符数组 : char arr1[5] //一维数组 char arr2[3][5] // 二维数组 C语言 中…

数据图表ScottPlot.WPF用法示例

目录 一、添加 NuGet 程序包&#xff08;5.0.47&#xff09; 二、MainWindow.xaml中添加引用 三、MainWindow.xaml.cs 具体使用代码 图表示例&#xff1a; 一、添加 NuGet 程序包&#xff08;5.0.47&#xff09; 二、MainWindow.xaml中添加引用 <Window x:Class"…

AtCoder Beginner Contest 001(A - 積雪深差、B - 視程の通報、C - 風力観測、D - 感雨時刻の整理)题目翻译

由于我发现网上很少有人会发很久之前AtCoder Beginner Contes的题&#xff0c;所以我打算从AtCoder Beginner Contest 001开始写。大约两周一更&#xff0c;需要的可以订阅专栏&#xff0c;感谢支持Thanks♪(&#xff65;ω&#xff65;)&#xff89; →题目讲解 A - 積雪深差 …

Windows 11【1001问】查看Windows是否激活的11种方法

在使用Windows 11的过程中&#xff0c;确保系统已正确激活是非常重要的一步。未激活的系统可能会限制某些功能的使用&#xff0c;并且无法获得最新的安全更新和支持。本文将详细介绍多种判断Windows 11是否已激活的11种方法&#xff0c;帮助用户快速了解自己的系统状态&#xf…

秒杀系统的常用架构是什么?怎么设计?

架构 秒杀系统需要单独部署&#xff0c;如果说放在订单服务里面&#xff0c;秒杀的系统压力太大了就会影响正常的用户下单。 常用架构&#xff1a; Redis 数据倾斜问题 第一步扣减库存时 假设现在有 10 个商品需要秒杀&#xff0c;正常情况下&#xff0c;这 10 个商品应该均…

USRP7440-通用软件无线电平台

1、产品描述 USRP7440基于第三代XILINX Zynq UltraScale RFSoC架构&#xff0c;它将射频ADC、DAC、ARM、FPGA等集成一体&#xff0c;瞬时带宽可以达到2.5GHz&#xff0c;尤其适合于射频直采应用&#xff0c;比如通信与雷达。 第一代RFSOC高达4GHz • 8x 或 16x 6.554GSPS DAC…

【Python机器学习】1.1. 机器学习(Machine Learning)介绍

喜欢的话别忘了点赞、收藏加关注哦&#xff0c;对接下来的教程有兴趣的可以关注专栏。谢谢喵&#xff01;(&#xff65;ω&#xff65;) 1.1.1. 什么是机器学习&#xff1f; 举个例子&#xff1a; 小明1月工资1000&#xff0c;每月增长10%&#xff0c;第10月是多少&#xff…

协议-Airkiss

是什么&#xff1f; 设备 A 与外界没有建立任何实质性连接&#xff0c;可以称之为信息孤岛。设备 B 通过路由 或者直接 将 Wifi 的 ssid 与密码 UDP广播 传递给 A 为什么&#xff1f; 解决将无线网络的 ssid 与密码传输到设备难题 怎么做&#xff1f; 芯片自带AT指令开启Air…

python第十一课:并发编程 | 多任务交响乐团

&#x1f3af; 本节目标 理解多线程/多进程/协程的应用场景掌握threading与multiprocessing核心用法学会使用asyncio进行异步编程开发实战项目&#xff1a;高并发爬虫引擎破解GIL锁的性能迷思 1️⃣ 并发编程三剑客 &#x1f3bb; 生活化比喻&#xff1a; 多线程 → 餐厅多个…

linux中断调用流程(arm)

文章目录 ARM架构下Linux中断处理全流程解析&#xff1a;从硬件触发到驱动调用 ⚡**一、中断触发与硬件层响应** &#x1f50c;**1. 设备触发中断** &#x1f4e1; **二、CPU阶段&#xff1a;异常入口与上下文处理** &#x1f5a5;️**1. 异常模式切换** &#x1f504;**2. 跳转…

Deepseek 模型蒸馏

赋范课堂&#xff1a; https://www.bilibili.com/video/BV1qUN8enE4c/

商城系统单商户开源版源码

环境配置 1.软件安装 宝塔安装系统软件:Nginx、MySQL5.6、PHP( PHP用7.1-7.4版本)、phpMyAdmin(Web端MySQL管理工具)。 2.配置mysql 设置mysql&#xff0c;在已安装的软件里面找到 mysql点击进行设置 3.修改sql-mode 选择左侧配置修改&#xff0c;找到里面的sql-mode&…

登录日志管理:通用分页和排序封装、 查询登录日志列表、删除登录日志、清空登录日志、解锁用户登录状态(解锁密码错误次数超限)

文章目录 引言I 登录日志管理接口列表II 通用分页和排序封装Java 分页和排序封装vue前端排序页面III 工具类字段名转换 : 驼峰转下划线命名引言 I 登录日志管理 接口列表 import request from @/utils/request// 查询登录日志列表 export function list(query) {return

Java内存管理与性能优化实践

Java内存管理与性能优化实践 Java作为一种广泛使用的编程语言&#xff0c;其内存管理和性能优化是开发者在日常工作中需要深入了解的重要内容。Java的内存管理机制借助于垃圾回收&#xff08;GC&#xff09;来自动处理内存的分配和释放&#xff0c;但要实现高效的内存管理和优…

Flutter_学习记录_实现列表上拉加载更多的功能

可以用ScrollController组件来实现这样列表上拉加载更多的功能: 1. 定义变量 在StatefulWidget 的组件内&#xff0c;添加三个属性&#xff1a; // 滚动视图的控制器final ScrollController _scrollController ScrollController();// 是否已显示了上拉加载中bool _isShowM…

使用DeepSeek+KIMI生成高质量PPT

一、使用DeepSeek DeepSeek官网&#xff1a;DeepSeek 点击“开始对话”&#xff0c;进入交互页面。 在上图中&#xff0c;输入问题&#xff0c;即可获取AI生成的结果。 基础模型&#xff08;V3&#xff09;&#xff1a;通用模型&#xff08;2024.12&#xff09;&#xff0c;高…

TCP和UDP比较

以下是 TCP&#xff08;传输控制协议&#xff09; 和 UDP&#xff08;用户数据报协议&#xff09; 的详细对比&#xff0c;涵盖核心特性、应用场景及技术差异&#xff1a; 1. 核心特性对比 特性TCPUDP连接方式面向连接&#xff08;需三次握手建立连接&#xff09;无连接&#…

Spring Boot 3.x 基于 Redis 实现邮箱验证码认证

文章目录 依赖配置开启 QQ 邮箱 SMTP 服务配置文件代码实现验证码服务邮件服务接口实现执行流程 依赖配置 <dependencies> <!-- Spring Boot Starter Web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spr…

(七)消息队列-Kafka 序列化avro(传递)

&#xff08;七&#xff09;消息队列-Kafka 序列化avro&#xff08;传递&#xff09; 客从远方来&#xff0c;遗我双鲤鱼。呼儿烹鲤鱼&#xff0c;中有尺素书。 ——佚名《饮马长城窟行》 本文已同步CSDN、掘金平台、知乎等多个平台&#xff0c;图片依然保持最初发布的水印&…