文章目录
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[i−1][j−1]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=0∣∣nums[i]>nums[i−1]∣∣nums[i]==nums[i−1]nums[i]<nums[i−1]
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=0∣∣nums[i]<nums[i−1]∣∣nums[i]==nums[i−1]nums[i]>nums[i−1]
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;
}
};