每日算法精讲 Day 5 | leetcode 209. 长度最小的子数组、leetcode 3. 无重复字符的最长子串、leetcode 1004. 最大连续1的个数 III

目录

引言:

209. 长度最小的子数组

题目分析

逻辑梳理

代码实现

复杂度分析

3. 无重复字符的最长子串

题目分析

逻辑梳理

代码实现

复杂度分析 

1004. 最大连续1的个数 III

题目分析

逻辑梳理

代码实现

复杂度分析

结语:


引言:

滑动窗口是处理数组/字符串子区间问题的经典技巧。本文将通过三道 LeetCode 高频题,由浅入深地讲解双指针滑动窗口的核心思想与应用技巧。

题目核心技巧
长度最小的子数组正数单调性 → 双指针收缩求最值
无重复字符的最长子串哈希判重 → 动态维护合法窗口
最大连续 1 的个数 III状态标记 → 转化约束条件

209. 长度最小的子数组

题目分析

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

逻辑梳理

由于数组元素均为正数,子数组的和具有单调性——窗口扩大时和增加,缩小时和减少。这一性质使得双指针滑动窗口成为天然的选择:

  • 维护窗口 [l, r),其中 l 为起始位置,r 为结束位置的下一个位置(左闭右开)
  • ret 记录当前窗口内元素之和
  • 若 ret < target,说明窗口还需扩大,r 右移并将新元素加入和
  • 若 ret >= target,说明当前窗口满足条件,更新最小长度,随后尝试收缩窗口:l 右移并将离开窗口的元素从和中扣除
  • 当 r 到达数组末尾后,当前窗口可能仍满足条件,需在外层继续收缩并更新答案

代码实现

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int len = 0x3f3f3f3f;
        int l = 0,r = 1;
        long long ret = nums[0];
        while(r<nums.size())
        {
            if(ret<target)
            {
                ret+=nums[r++];
            }
            else
            {
                len = min(r-l,len);
                ret-=nums[l++];
            }
        }
        while(ret>=target)
        {
            len = min(r-l,len);
            ret-=nums[l++];
        }
        return len==0x3f3f3f3f?0:len;
    }
};

复杂度分析

  • 时间复杂度:O(N),每个元素最多被 l 和 r 各访问一次
  • 空间复杂度:O(1),仅使用常数个额外变量

3. 无重复字符的最长子串

题目分析

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

逻辑梳理

本题与上一题思路一脉相承,核心差异在于窗口的合法性条件由"和 ≥ target"变为"无重复字符"。借助哈希表(或数组)记录字符出现次数,即可 O(1)判断重复:

  • st[200] 作为字符频次数组(ASCII 范围足够覆盖常见字符)
  • l 指向当前无重复子串的起始位置
  • 遍历右端点 i,若 s[i] 已存在于窗口中,则不断右移 l 直至 s[i] 不再重复
  • 每次将 s[i] 纳入窗口后,更新最大长度

代码实现

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int st[200]{};
        int l = 0,ans = 0;
        for(int i =0; i<s.size();i++)
        {
            while(st[s[i]])
            {
                st[s[l++]]--;
            }
            st[s[i]]++;
            ans = max(ans,i-l+1);
        }
        return ans;
    }
};

复杂度分析 

  • 时间复杂度:O(N),左右指针均单向移动,每个字符最多被访问两次
  • 空间复杂度:O(1),固定大小的频次数组,与输入规模无关

1004. 最大连续1的个数 III

题目分析

给定一个由若干 0 和 1 组成的数组 nums 以及整数 k,最多可以将 k 个 0 翻转为 1,返回最长的连续 1 的子数组长度。

逻辑梳理

本题是滑动窗口的变体应用:将"最多翻转 k 个 0"转化为"窗口内 0 的个数不超过 k"。为了优雅地处理翻转状态的回退,采用一个技巧——将翻转过的 0 标记为 2:

  • l 初始化为 -1(窗口起始前一个位置),r 为窗口结束位置
  • 遇到 nums[r] == 1:直接扩展窗口,更新答案
  • 遇到 nums[r] == 0 且还有剩余翻转次数 k:将其翻转为 2,k--,更新答案
  • 遇到 nums[r] == 0 但无剩余次数:右移 l 直至遇到第一个被翻转的 2,将其恢复为 0(k++ 的等价操作),随后将当前 r 位置的 0 翻转为 2。此过程中窗口长度不变,仅需继续右移 r

代码实现

class Solution {
public:
    int longestOnes(vector<int>& nums, int k) {
        int ans = 0;
        int l = -1,r = 0,tmp = k;
        while(r<nums.size())
        {
            if(nums[r]==0)
            {
                if(k)
                {
                    nums[r]=2;
                    k--;
                    ans = max(ans,r-l);
                }
                else
                {
                    while(k==0&&l<r)
                    {
                        l++;
                        if(nums[l]==2)
                        {
                            nums[r] = 2;
                            break;
                        }
                    }
                }
            }
            else if(nums[r]==1)
            {
                ans = max(ans,r-l);
            }
            r++;
        }
        return ans;
    }
};

复杂度分析

  • 时间复杂度:O(N),l 和 r 均最多遍历数组一次
  • 空间复杂度:O(1),原地修改数组,未使用额外数据结构

结语:

掌握滑动窗口的关键在于:识别问题的单调性,明确定义窗口的合法性条件,并保证双指针的单向移动。当遇到子数组/子串最值问题时,不妨优先考虑这一利器。

希望以上内容对你有所帮助,感谢观看,若觉得写的还可以,可以分享给朋友一起来看哦,毕竟一起进步更有动力嘛,当然能关注一下就更好啦。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值