刷题日志(1)——滑动窗口

本文详细介绍了四种与IT技术相关的子数组问题,包括长度最小的子数组、无重复字符的最长子串、最小覆盖子串和加油站问题,都运用了滑动窗口算法,通过维护单调关系求解。
摘要由CSDN通过智能技术生成


一、长度最小的子数组

在这里插入图片描述
思路:

  1. 设置左右窗口 l,r,连续子数组和为sum,返回的最小子数组长度为len;
  2. 遍历数组,每次将 nums[r]元素 加入sum,如sum<target,则一直往右遍历。
  3. 若sum>=target,则说明找到了>=target的子数组,那么尝试缩小窗口来找到满足条件的最小子数组,之后更新len。
  4. 判断len值进行返回
int minSubArrayLen(int target, int* nums, int numsSize) {
    //设置窗口指针
    int left=0;   //左侧窗口
    int right=-1; //右侧窗口
    int s=0;      //存储子数组的总和,用于判断target
    int len=INT32_MAX;   
    for(int i=0;i<numsSize;i++)
    {
        s+=nums[i];
        right++;      //每次遍历新元素加入s
        while(s>=target)     //满足条件
        {
            if(s-nums[left]>=target)   //尝试缩小窗口
            {
                s-=nums[left];
                left++;
            }
            else
            {
                break;               //已经不能在缩小
            }
             
        }
        if(s>=target)    //如果满足子数组条件判断len是否需要更新
            len=len>(right-left+1)?right-left+1:len;
    }
    return len==INT32_MAX?0:len;   
}

二、无重复字符的最长子串

在这里插入图片描述
思路:

  1. 对于S的任意一个字符,都可以转化为0-255的整数,因此建立字符表用于查找字符的上一次出现位置用于查找字符的上一次出现位置,如last[97]记录了字符’a’的上一次出现位置。

  2. 遍历S,如果S[r]在窗口中不存在(窗口记录了无重复的字符串),则加入窗口中,更新字符表。

  3. 如果S[r]在窗口中存在并且上一次出现位置在l-r内,则将l移到其下一位并更新字符表。

             (abc)a——>a(bca);  上一次位置在l-r中,l缩小至下一位。
    
  4. 如果S[r]在字符表中存在,但其上次出现在(l-r)外,则维持不变。

    	    ab(cbe)a ->ab(cbea);上一次位置在l-r外,l不变。
    
  5. 长度维护与上题同理

int max(int x,int y)
{
    return x>y?x:y;
}
int lengthOfLongestSubstring(char* str) {
    int n = strlen(str);   //计算字符串长度
    int last[256];         //定义字符数组,每一个字符都可以转化为在0~255内的整型变量。如;'a'的ascill码
    //为97,故该数组的last[97]记录了字符'a'在str字符串中上一次出现的位置。
    int ans = 0;      //返回答案

    for (int i = 0; i < 256; i++) {
        last[i] = -1;      //进行初始化为-1,表示此时各字符均未出现
    }
    for (int l = 0, r = 0; r < n; r++) 
    {
        l=max(l,last[str[r]]+1);   //如果上一次出现字符位置在l-r外(在l左边),则l不变。
        //如果在l-r内有是s[r]则,窗口更新,进行收缩,l位置来的该字符上一次出现的下一位,保证无重复字符。(abc)a->a(bca)
        ans=max(ans,r-l+1);         //比较此时是否比上一次更长
        last[str[r]]=r;           //更新字符上一次出现位置
    }
    return ans;
}

三.最小覆盖子串

在这里插入图片描述
思路:(想像欠债还钱过程)

  1. 首先,对建立字符表debts,然后用 t 字符串对字符表初始化,该字符表记录 t 的欠债数,t 中的字符每出现一次,欠债数就增加1,字符数组对于值减1,同时记录总欠债数 debt。定义start和len分别记录目标字符串的起始位置与长度。同样,定义左右窗口。
  2. 遍历字符串S,如果 debts[S[r]]<0,则债务还一份,字符数组相应位置值加一,总债务减少1。
  3. 若遇到了非欠债字符串,则其字符数组对于值加一,总债务不变。
  4. 若总债务=0,说明已经还清债务,即已经找到了符合条件的字符串。
  5. l 尝试向前移动,如果debts[S[l]]值大于0,说明该元素无关紧要,可以缩减。如果debts[S[l]]=0,说明此时已经不能移动,否则会欠款。
  6. 该过程更新len与start,最终返回目标字符串即可。
char* minWindow(char* s, char* t) {
    if(strlen(s)<strlen(t))
        return "";         //s长度比t小,不满足题意
    int debts[256]={0}; //字符表,记录t数组的各元素出现次数,一开始全为0
    int debt=strlen(t); //总次数
    int start=0;        //记录覆盖子串起始位置
    int len=INT_MAX;    //记录覆盖子串长度
    for(int i=0;i<debt;i++)
    {
        debts[t[i]]--; //初始化字符表数组,每出现一次,对应字符位值-1;
    }
    for(int left=0,right=0;right<strlen(s);right++)
    {
        if(debts[s[right]]++<0) //
            debt--;  
        //如果s[r]的元素在字符表中值为负,说明t字符串中有该元素,之后总次数减1
        if(debt==0)
        {
            while(debts[s[left]]>0)   //尝试缩小窗口
            {
                debts[s[left]]--;
                left++;
            }
            if(right-left+1<len)    //判断是否是更小的子串
            {
                len=right-left+1;
                start=left;
            }
        }
    }
    if(debt!=0)
        return "";
    char* str=malloc((len+1)*sizeof(char));    //构造子串
    for(int i=0;i<len;i++)
    {
        str[i]=s[start+i];
    }
    str[len]='\0';
    return str;
}

四. 加油站

在这里插入图片描述
思路:
与前面不同,该题从子数组开头开始讨论。

  1. 从头开始遍历每个加油站 l(窗口左边界),每次窗口加入新元素,计算其累加和是否>=0,如果符合,说明可以到达该位置,窗口继续扩大,如果再次回到起点,则符合题意返回起点。
  2. 否则不可达,l 到下一站点,窗口大小减少,累加和更新(少了一个元素),直到sum>=0。
  3. 遍历完成说明均不可达,返回-1
int canCompleteCircuit(int* gas, int gasSize, int* cost, int costSize) {
    //r:即将进来的数
    //len:窗口大小
    //sum窗口累加和
    int n=gasSize;
    for(int l=0,r=0,sum=0,len=0;l<n;l++)
    {
        while(sum>=0)
        {
            if(len==n)  //即能够到达起点
                return l;
            r=(l+(len++))%n;  //实现一圈
            sum+=gas[r]-cost[r];  //窗口增大,累加和更新
        }
        len--;     //当sum<0时,该站点不符合题意,到下一站点,窗口减小
        sum-=gas[l]-cost[l];  //窗口减小,累加和减去去掉的余量
    }
    return -1;   //遍历完说明均不符合题意
}

总结

滑动窗口

维持左右边界都不退回的一段范围,可以求解子数组/串的问题。

关键

找到窗口扩大/缩小情况下是否存在某种单调关系。 如:缩小时和减少,扩大时和增大。缩小时次数增大,扩大时次数减小等。

  • 41
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值