滑动窗口法

滑动窗口法

一、题源:

209. 长度最小的子数组
问题描述

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

找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, …, numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0

二、 思路:

1、暴力解法

最容易想到的方法就是直接对数组进行遍历,然后再从遍历的位置开始查找符合条件的子数组。
时间复杂度: O ( n 2 ) O(n^2 ) O(n2)
空间复杂度: O ( 1 ) O(1) O(1)
注:暴力法虽然思路简单,容易想到,但是时间复杂度太高,所以考虑有无简单的方法。

2、滑动窗口

实际上滑动窗口和之前的双指针法很像,也是两个指针控制程序的执行,只是写法略有不同,我自己实现的代码写出来就很像双指针法

参考代码如下


int minSubArrayLen(int target, int* nums, int numsSize){
    int headIndex = 0, rearIndex = 1;
    int sum = nums[0];
    int minLength = numsSize+1;
    while(1)
    {
        if(sum >= target)
        {
            if(minLength > rearIndex - headIndex)  minLength = rearIndex - headIndex; 
            sum -= nums[headIndex++];
        }
        else
        {
            if(rearIndex != numsSize) sum += nums[rearIndex++];
            else break;
        }
    }
    if(minLength == numsSize + 1)return 0;
    else return minLength;
}

通过headIndexrearIndex来记录下标,通过前后指针的移动对指针之间的数组元素的和进行相应变化,然后找到最终结果。

这样的写法有点理解起来比较困难,参考了其他人的写法,觉得另一种写法更好点。

参考代码如下


int minSubArrayLen(int target, int* nums, int numsSize){
    int result = INT32_MAX;
    int sum = 0; // 滑动窗口数值之和
    int headIndex = 0, rearIndex = 0;
    int minLength = 0;
    for (; rearIndex < numsSize; rearIndex++) {
        sum += nums[rearIndex];
        while (sum >= target) {
            minLength = (rearIndex - headIndex + 1); 
            result = result < minLength ? result : minLength;
            sum -= nums[headIndex++]; 
        }
    }
    return result == INT32_MAX ? 0 : result;
}

通过rearIndex前移引入新的元素,当两个下标内的数组元素和大于等于target后,再通过headIndex前移去掉旧的元素,同时更新minLength

总结:实际上两种写法在执行上只有微小的差别,但是从可阅读性上来说,后面一种要优于前面一种

时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( 1 ) O(1) O(1)

三、相关题目及C语言实现

904. 水果成篮

问题描述:

你正在探访一家农场,农场从左到右种植了一排果树。这些树用一个整数数组 fruits 表示,其中 fruits[i] 是第 i 棵树上的水果 种类 。

你想要尽可能多地收集水果。然而,农场的主人设定了一些严格的规矩,你必须按照要求采摘水果:

  • 你只有 两个 篮子,并且每个篮子只能装 单一类型 的水果。每个篮子能够装的水果总量没有限制。
  • 你可以选择任意一棵树开始采摘,你必须从 每棵 树(包括开始采摘的树)上 恰好摘一个水果 。采摘的水果应当符合篮子中的水果类型。每采摘一次,你将会向右移动到下一棵树,并继续采摘。
  • 一旦你走到某棵树前,但水果不符合篮子的水果类型,那么就必须停止采摘。

给你一个整数数组 fruits ,返回你可以收集的水果的 最大 数目。

实现:


int totalFruit(int* fruits, int fruitsSize){
    int headIndex = 0, rearIndex = 0;
    int maxLength = 0;
    int baskets[4];
    baskets[0] = -1;
    baskets[1] = 0;
    baskets[2] = -1;
    baskets[3] = 0;
    for(;rearIndex < fruitsSize; rearIndex++)
    {
        if(baskets[0] != fruits[rearIndex])
        {
            if(baskets[2] != fruits[rearIndex])
            {
                if(maxLength < baskets[1]+baskets[3]) maxLength = baskets[1]+baskets[3];
                while(baskets[1] != 0 && baskets[3] != 0)
                {
                    if(baskets[0] == fruits[headIndex++]) baskets[1]--;
                    else baskets[3]--;    
                }
                if(baskets[1] == 0) 
                {
                    baskets[0] = fruits[rearIndex];
                    baskets[1] = 1;
                }
                else
                {
                    baskets[2] = fruits[rearIndex];
                    baskets[3] = 1;
                }
            }
            else
            {
                baskets[3]++;
            }
        }
        else
        {
            baskets[1]++;
        }
    }  
    if(maxLength < baskets[1]+baskets[3]) maxLength = baskets[1]+baskets[3];
    return maxLength;
}

稍微做了一点改进:


int totalFruit(int* fruits, int fruitsSize){
    int headIndex = 0, rearIndex = 0;
    int maxLength = 0;
    int baskets[4];
    if(fruitsSize <= 2) return fruitsSize;
    baskets[0]=fruits[rearIndex++];
    baskets[1]= 1;
    while(baskets[0] == fruits[rearIndex++] && rearIndex < fruitsSize)baskets[1]++;
    if(rearIndex == fruitsSize) return fruitsSize;
    baskets[2]=fruits[rearIndex - 1];
    baskets[3]= 1;
    for(;rearIndex < fruitsSize; rearIndex++)
    {
        if(baskets[0] != fruits[rearIndex])
        {
            if(baskets[2] != fruits[rearIndex])
            {
                if(maxLength < baskets[1]+baskets[3]) maxLength = baskets[1]+baskets[3];
                while(baskets[1] != 0 && baskets[3] != 0)
                {
                    if(baskets[0] == fruits[headIndex++]) baskets[1]--;
                    else baskets[3]--;    
                }
                if(baskets[1] == 0) 
                {
                    baskets[0] = fruits[rearIndex];
                    baskets[1] = 1;
                }
                else
                {
                    baskets[2] = fruits[rearIndex];
                    baskets[3] = 1;
                }
            }
            else
            {
                baskets[3]++;
            }
        }
        else
        {
            baskets[1]++;
        }
    }  
    if(maxLength < baskets[1]+baskets[3]) maxLength = baskets[1]+baskets[3];
    return maxLength;
}

76. 最小覆盖子串

问题描述:

给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 “”

注意:

  • 对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
  • 如果 s 中存在这样的子串,我们保证它是唯一的答案。

实现:


int inStr(char a, char* t, int tLength)
{
    for(int i = 0; i < tLength; i++) if(a == t[i]) return 1;
    return 0;
}
void replace(char a, char* t, int* tPostion, int tLength, int* minLengthHeadIndex, int* minLengthRearIndex, int rearIndex)
{
    int minPostion = INT32_MAX, maxPostion = 0;
    int targetMinPostionIndex = 0;
    while(t[targetMinPostionIndex] != a) targetMinPostionIndex++;
    for(int i = 0; i < tLength; i++) if(t[i] == a && tPostion[i] < tPostion[targetMinPostionIndex]) targetMinPostionIndex = i;
    tPostion[targetMinPostionIndex] = rearIndex;
    for(int i = 0; i < tLength; i++) if(tPostion[i] > maxPostion) maxPostion = tPostion[i];
    for(int i = 0; i < tLength; i++) if(tPostion[i] < minPostion) minPostion = tPostion[i];
    if(maxPostion - minPostion < *minLengthRearIndex - *minLengthHeadIndex) 
    {
        *minLengthHeadIndex = minPostion;
        *minLengthRearIndex = maxPostion;
    }
}
int init(char a, char* t, int* tPostion, int tLength, int* minLengthHeadIndex, int* minLengthRearIndex, int rearIndex)
{
    int targetMinPostionIndex = 0;
    while(t[targetMinPostionIndex] != a) targetMinPostionIndex++;
    for(int i = 0; i < tLength; i++) if(t[i] == a && tPostion[i] < tPostion[targetMinPostionIndex]) targetMinPostionIndex = i;
    tPostion[targetMinPostionIndex] = rearIndex;
    // for(int i = 0; i < tLength; i++) printf("%d ",tPostion[i]);
    // printf("\n");
    for(int i = 0; i < tLength; i++) if(tPostion[i] == -1) return 0;
    for(int i = 0; i < tLength; i++) if(tPostion[i] > *minLengthRearIndex) *minLengthRearIndex = tPostion[i];
    for(int i = 0; i < tLength; i++) if(tPostion[i] < *minLengthHeadIndex) *minLengthHeadIndex = tPostion[i];
    return 1;
}
char* minWindow(char * s, char * t){
    int rearIndex = 0;
    int minLengthHeadIndex = INT32_MAX, minLengthRearIndex = 0;
    int tLength = strlen(t);
    int* tPostion = (int*)malloc(sizeof(int) * tLength);
    char * result;
    for(int i = 0; i < tLength; i++) tPostion[i] = -1;
    for(; s[rearIndex]; rearIndex++) 
        if(inStr(s[rearIndex], t, tLength))
            if(init(s[rearIndex], t, tPostion, tLength, &minLengthHeadIndex, &minLengthRearIndex, rearIndex)) break;
    if(!s[rearIndex])
    {
        result = (char*)malloc(sizeof(char));
        result[0] = 0;
        return result;
    }
    for(++rearIndex; s[rearIndex]; rearIndex++)if(inStr(s[rearIndex], t, tLength)) replace(s[rearIndex], t, tPostion, tLength, &minLengthHeadIndex, &minLengthRearIndex, rearIndex);
    result = (char*)malloc(sizeof(char)*(minLengthRearIndex - minLengthHeadIndex + 2));
    for(int i = 0; i <= minLengthRearIndex - minLengthHeadIndex; i++) result[i] = s[i + minLengthHeadIndex];
    result[minLengthRearIndex - minLengthHeadIndex + 1] = 0;
    return result;

}

这个实现方式实际上也算是滑动窗口,但是由于每次更新rearIndex指针后都需要在字符串t中查找,更新tPostion数组,以及遍历tPostion数组来更新minLengthRearIndexminLengthHeadIndex,导致时间复杂度为 O ( m n ) O(mn) O(mn),空间复杂度为 O ( m ) O(m) O(m)
这种实现通不过leetcode的最后一个测试用例,所以还要做改进。

实现(改进后):


int capitalLetters[26];
int letters[26];
void init(char* t)
{
    int i = 0;
    for(i = 0; i < 26; i++)
    {
        letters[i] = 0;
        capitalLetters[i] = 0;
    }
    i = 0;
    while(t[i])
    {
        if(t[i]<='z' && t[i] >='a') ++letters[t[i]-'a'];
        else ++capitalLetters[t[i]-'A'];
        ++i;
    }
    for(i = 0; i < 26; i++)
    {
        if(letters[i] == 0) letters[i] = INT32_MIN;
        if(capitalLetters[i] == 0) capitalLetters[i] = INT32_MIN;
    }

}
int catch(char a)
{
    if(a <='z' && a >='a')
    {
        if(letters[a-'a'] == INT32_MIN) return 0;
        --letters[a-'a'];
    }
    else
    {
        if(capitalLetters[a-'A'] == INT32_MIN) return 0;
        --capitalLetters[a-'A'];
    }
    return 1;
}
int noSurplus()
{
    for(int i = 0; i < 26; i++)
    {
        if(letters[i] > 0 || capitalLetters[i] > 0) return 0;
    }
    return 1;
}
void release(char a)
{
    if(a <='z' && a >='a')
    {
        if(letters[a-'a'] != INT32_MIN) ++letters[a-'a'];
    }
    else
    {
        if(capitalLetters[a-'A'] != INT32_MIN) ++capitalLetters[a-'A'];
    }
}
char* minWindow(char * s, char * t){
    int headIndex = 0, rearIndex = 0;
    int minLengthHeadIndex = 0, minLengthRearIndex = INT32_MAX;
    char * result;
    init(t);
    for(; s[rearIndex]; ++rearIndex)
    {
        if(!catch(s[rearIndex])) continue;
        while(noSurplus())
        {
            if(rearIndex - headIndex < minLengthRearIndex - minLengthHeadIndex)
            {
                minLengthRearIndex = rearIndex;
                minLengthHeadIndex = headIndex;
            }
            release(s[headIndex++]);
        }
    }
    if(minLengthRearIndex == INT32_MAX)
    {
        result = (char*)malloc(sizeof(char));
        result[0] = 0;
        return result;
    }
    else
    {
        result = (char*)malloc(sizeof(char)*(minLengthRearIndex - minLengthHeadIndex + 2));
        for(int i = 0; i <= minLengthRearIndex - minLengthHeadIndex; i++) result[i] = s[i + minLengthHeadIndex];
        result[minLengthRearIndex - minLengthHeadIndex + 1] = 0;
        return result;
    }
}

通过capitalLettersletters数组来记录t中出现的字母的频次,这样的话每次更新rearIndexheadIndex只需要遍历capitalLettersletters数组,这样的话时间复杂度是 O ( n ) O(n) O(n),空间复杂度是 O ( 1 ) O(1) O(1)

你可能感兴趣的:(#,数组,数据结构和算法,leetcode,算法,c++)