七月集训(6)滑动窗口+动态规划

本文围绕LeetCode的几道题目展开。包括643题用滑动窗口求子数组最大平均数;718题用动态规划求最长重复子数组;978题用动态规划求最长湍流子数组,还探讨了状态转移及维度优化;1052题用前缀和加滑动窗口求书店老板营业时最多满意客户数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.LeetCode:643. 子数组最大平均数 I

原题链接


        给你一个由 n 个元素组成的整数数组 nums 和一个整数 k 。

        请你找出平均数最大且 长度为 k 的连续子数组,并输出该最大平均数。

        任何误差小于 10-5 的答案都将被视为正确答案。

        示例 1:

        输入:nums = [1,12,-5,-6,50,3], k = 4

        输出:12.75

        示例 2:

        输入:nums = [5], k = 1

        输出:5.00000

        提示:

        1 <= k <= n <= 1e5

        -1ee4 <= nums[i] <= 1e4


        这道题就是典型的滑动窗口应用题了,维护一个k长度的滑动窗口,既然要求平均值最大那么我们就找到一个元素和最大的窗口,最后返回平均值即可。

class Solution {
public:
    double findMaxAverage(vector<int>& nums, int k) {
            double ans=-0x3f3f3f3f;
            int l=0,r=-1;
            double sum=0;
            while(r<(int)nums.size()-1){
                ++r;
                sum+=nums[r];
                while(r-l+1>k){
                    sum-=nums[l++];
                }
                if(r-l+1==k){
                    ans=max(sum,ans);
                }
            }
            return ans*1.0/k;
    }
};

2.LeetCode:718. 最长重复子数组

原题链接


        给两个整数数组 nums1 和 nums2 ,返回 两个数组中 公共的 、长度最长的子数组的长度 。

        示例 1:

        输入:nums1 = [1,2,3,2,1], nums2 = [3,2,1,4,7]

        输出:3

        示例 2:

        输入:nums1 = [0,0,0,0,0], nums2 = [0,0,0,0,0]

        输出:5

        提示:

        1 <= nums1.length, nums2.length <= 1000

        0 <= nums1[i], nums2[i] <= 100


        本题既可以滑动窗口也可以动态规划,不过滑动窗口比较复杂这里就介绍动态规划的做法了。

        我们定义DP数组dp[i][j],他代表以nums1[i]和nums[2]结尾的最大公共元素个数,那么很显然状态在nums1[i]==nums[j]的时候只能由dp[i-1][j-1]转移过来,而如果二者不相等自然就是0.
d p [ i ] [ j ] = { d p [ i − 1 ] [ j − 1 ] i , j > 0 , n u m s 1 [ j ] = n u m s [ j ] 0 n u m s 1 [ i ] ! = n u m s 2 [ j ] n u m s [ 1 ] = = n u m s 2 [ j ] ? 1 : 0 i , j = = 0 dp[i][j]=\begin{cases} dp[i-1][j-1] & i,j>0,nums1[j]=nums[j]\\ 0&nums1[i]!=nums2[j]\\ nums[1]==nums2[j]?1:0&i,j==0 \end {cases} dp[i][j]=dp[i1][j1]0nums[1]==nums2[j]?1:0i,j>0,nums1[j]=nums[j]nums1[i]!=nums2[j]i,j==0

        那么根据这个状态转移方程枚举i,j即可

class Solution {
    int dp[1010][1010];
public:
    int findLength(vector<int>& nums1, vector<int>& nums2) {
        int n=nums1.size();
        int m=nums2.size();
        int ans=0;
        for(int i=0;i<n;++i){
            for(int j=0;j<m;++j){
                if(!i||!j){
                    dp[i][j]=nums1[i]==nums2[j]?1:0;
                }else {
                    dp[i][j]=nums1[i]==nums2[j]?dp[i-1][j-1]+1:0;
                }
                ans=max(ans,dp[i][j]);
            }
        }
        return ans;
    }
};

3.LeetCode:978. 最长湍流子数组

原题链接


        给定一个整数数组 arr ,返回 arr 的 最大湍流子数组的长度 。

        如果比较符号在子数组中的每个相邻元素对之间翻转,则该子数组是 湍流子数组 。

        更正式地来说,当 arr 的子数组 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]。

        示例 1:

        输入:arr = [9,4,2,10,7,8,8,1,9]

        输出:5

        示例 2:

        输入:arr = [4,8,12,16]

        输出:2

        示例 3:

        输入:arr = [100]

        输出:1

        提示:

        1 <= arr.length <= 4 * 1e4

        0 <= arr[i] <= 1e9


        本题也是很显然是动态规划了,那么我们先定义数组,dp[i][j],这里的含义就有些难以想到了,这个数组的第一维就是以i结尾,而第二维代表着该数组当前的状态,具体的说来比如nums[i]>nums[i-1]的话代表是上升状态,那么该维度是1,如果nums[i]<nums[i-1]代表是下降状态,该维度为0。相等的时候不符合题目要求,不用管

        那么dp的含义自然也很明显了,他代表着以i结尾并且最后一对元素上升或下降状态的最长湍流元素个数。

        那么状态该怎么转移呢,这里也很简单,如果当前是上升状态那么他只能有下降状态转移而来,也就是dp[i][1]=dp[i-1][0]+1,而如果是下降状态那么他只能由上升状态转移而来,也就是dp[i][0]=dp[i-1][1]+1。最后我们返回max(dp[n][0],dp[n][1])即可。

        不过这里我们会发现,和背包问题类似,状态转移的时候仅仅涉及到i-1组的状态,也就是上一层的状态,那么是不是可以跟背包问题类似把某个维度优化掉呢?答案是可以的,因为我们在将要维护下一层的时候现在的状态自然就变成了dp[i-1]。不过这也只是代表可以优化,怎么解题才是关键。

        现在状态该如何转移?这里我们就发现了,如果想要维护数组无论如何都绕不开上升和下降状态的最大数目,所以需要用dp数组或者额外的标记来记录某个状态的最大个数,这里就选择空间复杂度较小的dp数组了。

        dp数组有两个元素,dp[1]代表上升状态的最大个数,dp[1]代表下降状态的最大个数,我们从头开始枚举数组,首先i==0的时候dp[0]=dp[1]=0,接下来如果nums[i]>nums[i-1],那么dp[1]=dp[0]+1,dp[0]=1,如果nums[i]<nums[i-1],dp[0]=dp[1]+1,dp[1]=0,形象的来说看下图:
d p [ 0 ] = { 1 i = 0 ∣ ∣ n u m s [ i ] > n u m s [ i − 1 ] ∣ ∣ n u m s [ i ] = = n u m s [ i − 1 ] d p [ 1 ] + 1 n u m s [ i ] < n u m s [ i − 1 ] dp[0]=\begin{cases} 1 &i=0||nums[i]>nums[i-1]||nums[i]==nums[i-1]\\ dp[1]+1&nums[i]<nums[i-1] \end{cases} dp[0]={1dp[1]+1i=0nums[i]>nums[i1]nums[i]==nums[i1]nums[i]<nums[i1]

d p [ 1 ] = { 1 i = 0 ∣ ∣ n u m s [ i ] < n u m s [ i − 1 ] ∣ ∣ n u m s [ i ] = = n u m s [ i − 1 ] d p [ 0 ] + 1 n u m s [ i ] > n u m s [ i − 1 ] dp[1]=\begin{cases} 1 &i=0||nums[i]<nums[i-1]||nums[i]==nums[i-1]\\ dp[0]+1&nums[i]>nums[i-1] \end{cases} dp[1]={1dp[0]+1i=0nums[i]<nums[i1]nums[i]==nums[i1]nums[i]>nums[i1]

class Solution {
public:
    int maxTurbulenceSize(vector<int>& arr) {
        int ans=1;
        int dp[2];
        dp[0]=dp[1]=1;
        for(int i=1;i<arr.size();++i){
            if(arr[i]>arr[i-1]){
                dp[1]=dp[0]+1;
                dp[0]=1;
            }else if(arr[i]<arr[i-1]){
                dp[0]=dp[1]+1;
                dp[1]=1;
            }else dp[0]=dp[1]=1;//这里可以用三目操作符写的更简洁,不过这样写更好理解
            ans=max(ans,max(dp[0],dp[1]));
        }
        return ans;
    }
};

4.LeetCode:1052. 爱生气的书店老板

原题链接


        有一个书店老板,他的书店开了 n 分钟。每分钟都有一些顾客进入这家商店。给定一个长度为 n 的整数数组 customers ,其中 customers[i] 是在第 i 分钟开始时进入商店的顾客数量,所有这些顾客在第 i 分钟结束后离开。

        在某些时候,书店老板会生气。 如果书店老板在第 i 分钟生气,那么 grumpy[i] = 1,否则 grumpy[i] = 0。

        当书店老板生气时,那一分钟的顾客就会不满意,若老板不生气则顾客是满意的。
书店老板知道一个秘密技巧,能抑制自己的情绪,可以让自己连续 minutes 分钟不生气,但却只能使用一次。

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

        示例 1:

        输入:customers = [1,0,1,2,1,1,7,5], grumpy = [0,1,0,1,0,1,0,1], minutes = 3

        输出:16

        示例 2:

        输入:customers = [1], grumpy = [0], minutes = 1

        输出:1

        提示:

        n == customers.length == grumpy.length

        1 <= minutes <= n <= 2 e4

        0 <= customers[i] <= 1000

        grumpy[i] == 0 or 1


        本题就是典型的前缀和加滑动窗口的应用了,我们知道利用前缀和可以快速求出数组的区间和本题就是利用了这个特性来做的。

        首先我们定义前缀和数组prev用来统计顾客在老板不生气的时候的满意度前缀和,gprev来统计根据老板生气状况的顾客满意度前缀和。

        那么由于老板每次只能使得自己在minutes分钟内不生气,那么我们就维护一个minutes长度的窗口[i,j],在这个窗口内老板是不生气的,其他分钟状况依旧。那么现在时间就分为了该窗口和该窗口左右这三个区间。该窗口内的顾客满意度就是prev[j]-prev[i-1],而除去该区间内所有时间老板都是依旧,这两个区间顾客满意度总和就为gprev[n]-(gprev[j]-gprev[i-1]),把这两个结果加起来就是当前顾客的满意度,我们维护最大值即可。

class Solution {
    #define maxn 20005
public:
    int maxSatisfied(vector<int>& customers, vector<int>& grumpy, int minutes) {
        int prev[maxn];
        int gprev[maxn];
        memset(prev,0,sizeof(prev));
        memset(gprev,0,sizeof(prev));
        int n=customers.size();
        for(int i=1;i<=n;i++){
            prev[i]=prev[i-1]+customers[i-1];
            gprev[i]=gprev[i-1]+(!grumpy[i-1])*customers[i-1];
        }
        int i=1,j=0,ans=0;
        while(j<n){
            ++j;
            while(j-i+1>minutes){
                ++i;
            }
            if(j-i+1==minutes){
                int val=gprev[n]-(gprev[j]-gprev[i-1])+(prev[j]-prev[i-1]);
                ans=max(ans,val);
            }
        }   
        return ans;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值