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

news2025/3/6 14:27:12

目录

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

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

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

相关文章

【最大半连通子图——tarjan求最大连通分量,拓扑排序,树形DP】

题目 分析 最大连通分量肯定是满足半连通分量的要求&#xff0c;因此tarjan。 同时为了简化图&#xff0c;我们进行缩点&#xff0c;图一定变为拓扑图。 我们很容易看出&#xff0c;只要是一条不分叉的链&#xff0c;是满足条件的。 于是我们按照拓扑序不断树形DP 建边注意…

一周学会Flask3 Python Web开发-在模板中渲染WTForms表单视图函数里获取表单数据

锋哥原创的Flask3 Python Web开发 Flask3视频教程&#xff1a; 2025版 Flask3 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili 为了能够在模板中渲染表单&#xff0c;我们需要把表单类实例传入模板。首先在视图函数里实例化表单类LoginForm&#xff0c;然…

DeepSeek R1助力,腾讯AI代码助手解锁音乐创作新

目录 1. DeepSeekR1模型简介2. 歌词创作流程2.1 准备工作2.2 歌词生成技巧 3. 音乐制作环节3.1 主流AI音乐生成平台 4. 歌曲欣赏5. 总结展望 1. DeepSeekR1模型简介 腾讯AI代码助手最新推出的DeepSeekR1模型不仅在代码生成方面表现出色&#xff0c;其强大的自然语言处理能力也…

【微信小程序】每日心情笔记

个人团队的比赛项目&#xff0c;仅供学习交流使用 一、项目基本介绍 1. 项目简介 一款基于微信小程序的轻量化笔记工具&#xff0c;旨在帮助用户通过记录每日心情和事件&#xff0c;更好地管理情绪和生活。用户可以根据日期和心情分类&#xff08;如开心、平静、难过等&#…

前端基础之组件

组件&#xff1a;实现应用中局部功能代码和资源的集合 非单文件组件 <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <meta name"viewport" content"widthdevice-width, initial-scale1.0"…

spring boot整合flyway实现数据的动态维护

1、简单介绍一下flyway Flyway 是一款开源的数据库版本控制工具&#xff0c;主要用于管理数据库结构的变更&#xff08;如创建表、修改字段、插入数据等&#xff09;。它通过跟踪和执行版本化的迁移脚本&#xff0c;帮助团队实现数据库变更的自动化。接下来简单介绍一下flyway…

数据结构知识学习小结

一、动态内存分配基本步骤 1、内存分配简单示例&#xff1a; 个人对于示例的理解&#xff1a; 定义一个整型的指针变量p&#xff08;着重认为它是一个“变量”我觉得可能会更好理解&#xff09;&#xff0c;这个变量用来存地址的&#xff0c;而不是“值”&#xff0c;malloc函…

【Spring AOP】_切点类的切点表达式

目录 1. 根据方法签名匹配编写切点表达式 1.1 具体语法 1.2 通配符表达规范 2. 根据注解匹配编写切点表达式 2.1 实现步骤 2.2 元注解及其常用取值含义 2.3 使用自定义注解 2.3.1 编写自定义注解MyAspect 2.3.2 编写切面类MyAspectDemo 2.3.3 编写测试类及测试方法 在…

初次使用 IDE 搭配 Lombok 注解的配置

前言 在 Java 开发的漫漫征程中&#xff0c;我们总会遇到各种提升效率的工具。Lombok 便是其中一款能让代码编写变得更加简洁高效的神奇库。它通过注解的方式&#xff0c;巧妙地在编译阶段为我们生成那些繁琐的样板代码&#xff0c;比如 getter、setter、构造函数等。然而&…

关于 QPalette设置按钮背景未显示出来 的解决方法

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/146047054 长沙红胖子Qt&#xff08;长沙创微智科&#xff09;博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV…

mysql下载与安装

一、mysql下载&#xff1a; MySQL获取&#xff1a; 官网&#xff1a;www.mysql.com 也可以从Oracle官方进入&#xff1a;https://www.oracle.com/ 下载地址&#xff1a;https://downloads.mysql.com/archives/community/ 选择对应的版本和对应的操作系统&#xff…

Python练习(握手问题,进制转换,日期问题,位运算,求和)

一. 握手问题 代码实现 ans0for i in range(1,51):for j in range(i1,51):if i<7 and j<7:continueelse:ans 1print(ans) 这道题可以看成是50个人都握了手减去7个人没握手的次数 答案&#xff1a;1204 二.将十进制整数拆解 2.1门牌制作 代码实现 ans0for i in ra…

小程序分类页面

1创建cate分支 2.cate滑动界面布局 获取滑动界面高度 3.获取并渲染一级分类的列表数据 4.渲染二级和三级分类列表 获取二级列表的数据 5.渲染二级分类列表的UI结构 6.动态渲染三级分类列表

HTML + CSS 题目

1.说说你对盒子模型的理解? 一、是什么 对一个文档进行布局的时候&#xff0c;浏览器渲染引擎会根据标准之一的css基础盒模型&#xff0c;将所有元素表示为一个个矩形的盒子。 一个盒子由四个部分组成: content&#xff0c;padding&#xff0c;border&#xff0c;margin 下…

计算机视觉|ViT详解:打破视觉与语言界限

一、ViT 的诞生背景 在计算机视觉领域的发展中&#xff0c;卷积神经网络&#xff08;CNN&#xff09;一直占据重要地位。自 2012 年 AlexNet 在 ImageNet 大赛中取得优异成绩后&#xff0c;CNN 在图像分类任务中显示出强大能力。随后&#xff0c;VGG、ResNet 等深度网络架构不…

Node JS 调用模型Xenova_all-MiniLM-L6-v2实战

本篇通过将句子数组转换为句子的向量表示&#xff0c;并通过平均池化和归一化处理&#xff0c;生成适合机器学习或深度学习任务使用的特征向量为例&#xff0c;演示通过NodeJS 的方式调用Xenova/all-MiniLM-L6-v2 的过程。 关于 all-MiniLM-L6-v2 的介绍&#xff0c;可以参照上…

ASP.NET Core JWT认证与授权

1.JWT结构 JSON Web Token&#xff08;JWT&#xff09;是一种用于在网络应用之间安全传输声明的开放标准&#xff08;RFC 7519&#xff09;。它通常由三部分组成&#xff0c;以紧凑的字符串形式表示&#xff0c;在身份验证、信息交换等场景中广泛应用。 2.JWT权限认证 2.1添…

【车规芯片】如何引导时钟树生长方向

12nm车规DFTAPR项目中&#xff0c;我们可以看到&#xff0c;绝大部分的sink都受控于xxxx_tessent_occ_clk_cpu_inst/tessent_persistent_cell_clock_out_mux/C10_ctmi_1这个mux&#xff0c;这是我们DFT设计结果&#xff1a; 这里我们重新打开place的数据 Anchor&#xff0c;也就…

《深度学习实战》第11集:AI大模型压缩与加速

深度学习实战 | 第11集&#xff1a;AI大模型压缩与加速 在深度学习领域&#xff0c;随着模型规模的不断增大&#xff0c;模型的推理速度和部署效率成为实际应用中的关键挑战。本篇博客将带你深入了解模型压缩与加速的核心技术&#xff0c;并通过一个实战项目展示如何使用知识蒸…

大语言模型中温度参数(Temperature)的核心原理

大语言模型中温度参数&#xff08;Temperature&#xff09;的核心原理是通过调整模型输出的概率分布&#xff0c;控制生成结果的随机性和多样性。以下是其原理的详细说明&#xff1a; 一、定义与核心作用 温度参数是生成式模型&#xff08;如GPT系列&#xff09;中的一个超参数…