leetcode 滑动窗口小结 (三)

129 阅读3分钟

目录

978. 最长湍流子数组

leetcode-cn.com/problems/lo…

题目

当 A 的子数组 A[i], A[i+1], …, A[j] 满足下列条件时,我们称其为湍流子数组:

若 i <= k < j,当 k 为奇数时, A[k] > A[k+1],且当 k 为偶数时,A[k] < A[k+1];
或 若 i <= k < j,当 k 为偶数时,A[k] > A[k+1] ,且当 k 为奇数时, A[k] < A[k+1]。
也就是说,如果比较符号在子数组中的每个相邻元素对之间翻转,则该子数组是湍流子数组。

返回 A 的最大湍流子数组的长度。

示例1:

输入:[9,4,2,10,7,8,8,1,9]
输出:5
解释:(A[1] > A[2] < A[3] > A[4] < A[5])

示例2:

输入:[4,8,12,16]
输出:2

示例 3:

输入:[100]
输出:1

提示:
1 <= A.length <= 40000
0 <= A[i] <= 10^9

思路分析以及代码

这一题是典型的滑动窗口问题。
我的大致思路如下:
1、获取差分数组
2、从左到右比对差分数组,观察相邻两个元素是否异号(一个大于0 ,一个小于0;或者相反)
3、如果异号,说明符合条件;更新窗口值,右边界继续扩展
4、否则,说明当前[left,right)中都不符合条件,需要直接将左边界更新到原有的右边界。
5、意外情况:下面有几种意外情况我是特殊处理的

1、原数组小于等于1,直接返回数组大小
2、如果差分数组中全部都是0,此时返回1

class Solution {
public:
    int juge(int a , int b)
    {
        if((a > 0 && b < 0) || (a < 0 && b >0)) return true;
        else return false;
    }
    int maxTurbulenceSize(vector<int>& arr) {
        if(arr.size() <= 1) return arr.size();
        int n = arr.size();
        vector<int> diff(n - 1);
        for(int i = 0; i < n - 1; i++)
        {
            diff[i] = arr[i+1] - arr[i];
        }
        int left = 0 ,right = 1;
        int max_window_size = 1;
        int zero_diff_times = 0;
        if(diff[0] == 0) zero_diff_times++;
        while(right < diff.size())
        {
           if(juge(diff[right],diff[right - 1]))
           {
                max_window_size = max(max_window_size,right - left + 1);
           }
           else
           {
               if(diff[right] == 0) zero_diff_times++;
               left = right;
           }
           right++;
        }
        if(zero_diff_times == diff.size()) return 1;
        return max_window_size+1;
    }
};

1052. 爱生气的书店老板

leetcode-cn.com/problems/gr…

题目

今天,书店老板有一家店打算试营业 customers.length 分钟。每分钟都有一些顾客(customers[i])会进入书店,所有这些顾客都会在那一分钟结束后离开。

在某些时候,书店老板会生气。 如果书店老板在第 i 分钟生气,那么 grumpy[i] = 1,否则 grumpy[i] = 0。 当书店老板生气时,那一分钟的顾客就会不满意,不生气则他们是满意的。

书店老板知道一个秘密技巧,能抑制自己的情绪,可以让自己连续 X 分钟不生气,但却只能使用一次。

请你返回这一天营业下来,最多有多少客户能够感到满意的数量。
示例:

输入:customers = [1,0,1,2,1,1,7,5], grumpy = [0,1,0,1,0,1,0,1], X = 3
输出:16
解释:
书店老板在最后 3 分钟保持冷静。
感到满意的最大客户数量 = 1 + 1 + 1 + 1 + 7 + 5 = 16.

提示:

1 <= X <= customers.length == grumpy.length <= 20000
0 <= customers[i] <= 1000
0 <= grumpy[i] <= 1

思考分析与初步代码

初步思路:
1、从第一个时间段开始遍历,遍历每个窗口长度为X的时间段,并且计算这个时间段内老板生气的时间顾客来的人数。
2、依次遍历完整个时间段,找到时间段内老板生气的时间顾客来的人数最多的人数。
3、最后再遍历一遍原数组,记录下老板不生气时候来的总人数
4、两个结果相加,得到最终结果。

class Solution {
public:
    int maxSatisfied(vector<int>& customers, vector<int>& grumpy, int X) {
        int max_sum_start = 0;
        int max_sum = 0;
        for(int start = 0; start < customers.size() - X + 1; start++)
        {
            int sum = 0;
            for(int i = start; i < start + X ; i ++)
            {
                if(grumpy[i] == 1) sum += customers[i];
            }
            if(sum >= max_sum)
            {
                max_sum = sum;
                max_sum_start = start;
            }
        }
        //cout << "max_sum= " << max_sum << endl;
        int result = 0;
        for(int i = 0; i < customers.size(); i++)
        {
            if(grumpy[i] == 0) result+=customers[i];
        }
        return result+max_sum;
    }
};

很显然时间复杂度是O(N * X)

优化思路以及优化代码

关于长度固定的滑动窗口,有一点我们要熟悉的是,窗口每次滑动时,变化的只有头尾两个元素,所以我们只针对这两个元素进行处理就行。
不过对于第一个窗口需要特殊对待,需要先进行填充。
接下来的窗口中,如果新进来的时间是生气的,加上顾客量。如果新出去的时间是生气的,那么减去顾客量。每次滑动都需要更新最大值。

class Solution {
public:
    int maxSatisfied(vector<int>& customers, vector<int>& grumpy, int X) {
        int sum = 0;
        //计算不生气时来的顾客总数
        for(int i = 0; i < customers.size(); i++)
        {
            if(grumpy[i] == 0) sum+=customers[i];
        }
        int left = 0, right = 0;
        //第一个区域
        while(right < X)
        {
            if(grumpy[right] == 1) sum += customers[right];
            right++;
        }
        int max_sum = sum;
        while(right < customers.size())
        {
            //生气的时间段被移除窗口,总顾客减少
            if(grumpy[left] == 1)   sum -= customers[left];
            //生气的时间段被移入窗口,总顾客增加
            if(grumpy[right] == 1) sum += customers[right];
            max_sum = max(max_sum,sum);
            left++;
            right++;
        }
        return max_sum;
    }
};

1208. 尽可能使字符串相等

题目

给你两个长度相同的字符串,s 和 t。

将 s 中的第 i 个字符变到 t 中的第 i 个字符需要 |s[i] - t[i]| 的开销(开销可能为 0),也就是两个字符的 ASCII 码值的差的绝对值。

用于变更字符串的最大预算是 maxCost。在转化字符串时,总开销应当小于等于该预算,这也意味着字符串的转化可能是不完全的。

如果你可以将 s 的子字符串转化为它在 t 中对应的子字符串,则返回可以转化的最大长度。

如果 s 中没有子字符串可以转化成 t 中对应的子字符串,则返回 0。

示例 1:

输入:s = “abcd”, t = “bcdf”, cost = 3
输出:3
解释:s 中的 “abc” 可以变为 “bcd”。开销为 3,所以最大长度为 3。

示例 2:

输入:s = “abcd”, t = “cdef”, cost = 3
输出:1
解释:s 中的任一字符要想变成 t 中对应的字符,其开销都是 2。因此,最大长度为 1。

示例 3:

输入:s = “abcd”, t = “acde”, cost = 0
输出:1
解释:你无法作出任何改动,所以最大长度为 1。

提示:

1 <= s.length, t.length <= 10^5
0 <= maxCost <= 10^6
s 和 t 都只含小写英文字母。

思考分析以及代码

这一题是一个非常典型的滑动窗口题。
1、首先我们构建差分绝对值数组:

vector<int> diff(s.size());
for(int i = 0; i < diff.size(); i++)
{
    diff[i] = abs(s[i] - t[i]);
}

2、然后,我们建立滑动窗口的左右边界,在差分数组上移动
3、什么时候扩展窗口?
当我们现在存有的cost - 右边新纳入的cost >= 0 的时候,我们可以继续扩展窗口
4、反之,如果小于0,说明不能继续扩展了。但是由于我们求的是最大窗口长度,所以此时也不需要缩小窗口,只需要将整个窗口平移即可。记住平移的时候,要将新移除的cost加到当前cost上,算是弥补损失。
5、更新窗口最大长度的操作必然是在扩展窗口的时候啦
这一题思路应该还是比较清晰的。当然也有个地方有点不好搞,那就是第三点是>0 还是 >=0.如果有时间的话,可以自己手动推导一下,如果没有就两个都试一下,选择答案正确的即可.

class Solution {
public:
    int equalSubstring(string s, string t, int maxCost) {
        //构建相差数组
        vector<int> diff(s.size());
        for(int i = 0; i < diff.size(); i++)
        {
            diff[i] = abs(s[i] - t[i]);
        }
        int left = 0, right = 0;
        int max_length = 0;
        while(right < diff.size())
        {
            //扩大窗口
            int new_In = diff[right];
            maxCost = maxCost - new_In;
            if(maxCost >= 0)
            {
                max_length = max(max_length,right -left + 1);
            }
            else
            {
                int new_Out = diff[left];
                maxCost += new_Out;
                left++;
            }
            right++;
        }
        return max_length;
    }
};