前言
滑动窗口:他是双指针的兄弟,咱们可以理解为一种有条件的双指针,他的两个指针被创建出来就是为了执行一个任务,这个任务可能是统计指针间的元素之和,亦或者是达到某一个条件不能就得换一种操作执行,然后又换回来,类似与取数据和收数据,收满了就得取,取空了就得收。
前缀和:前缀和的思想是重复利用计算过的子数组之和,从而降低区间查询需要累加计算的次数。前缀和 在涉及计算区间和的问题时非常有用!本质上就是空间换时间的做法,这个类似的做法在二叉树的递归操作里面很常见。
题目链接
一、长度最小的子数组
最简单的滑动窗口模型:所有滑动窗口的基础,固定一个左值向里面放数据,达到条件就进行更新操作,我们其实可以直接双循环把所有的情况都列举出来,然后我们来进行选择,但是这样做的时间复杂度过高,滑动窗口就是可以直接排除一些不必要的循环,举一个简单的例子。
//暴力解法
int minSubArrayLen(int target, vector<int>& nums) {
int n=nums.size(),ans=INT32_MAX,sum=0;
for(int i=0;i<n;i++){
sum=0;
for(int j=i;j<n;j++){
sum+=nums[j];
if(sum>=target){
ans=min(ans,j-i+1);
break;
}
}
}
return ans<n?ans:0;
}
int minSubArrayLen1(int target, vector<int>& nums) {
int n=nums.size(),ans=INT32_MAX,sum=0,left=0;
for(int right=0;right<n;right++){
sum+=nums[right];
while(sum>=target){
ans=min(ans,right-left+1);
sum-=nums[left++];
}//内层循环更新数据
}
return ans<=n?ans:0;
}
int minSubArrayLen2(int target, vector<int>& nums) {
int n=nums.size(),ans=INT32_MAX,left=0,sum=0;
for(int right=0;right<n;right++){
sum+= nums[right];
while(sum-nums[left]>=target) sum-=nums[left++];
if(sum>=target)ans=min(ans,right-left+1);
}//外层循环处理问题
return ans<=n?ans:0;
}
提升题目:水果成篮904. 水果成篮 - 力扣(LeetCode)
思路:哈希表(数组)和滑动窗口的结合,利用哈希表进行词频统计,用滑动窗口的思路去解答题目的意思。记录每种水果的数量,每次固定左端点,同时移动右端点,直到范围内的水果种数超过2,此时更新答案,左端点每次次循环只移动一个单位。
int totalFruit(vector<int>& fruits) {
//思路哈希加滑动窗口
unordered_map<int,int> map;
int res=0,n=fruits.size();
for(int i=0,j=0;i<n;i++){
map[fruits[i]]++;
while(map.size()>2){
map[fruits[j]]--;
if(map[fruits[j]]==0)map.erase(fruits[j]);
j++;
}
res=max(res,i-j+1);
}
return res;
}
类似题目:LCP 68. 美观的花束 - 力扣(LeetCode)
思路:定义一个统计个数的数组或者哈希表都可以,然后用右边界去统计个数,左边界去满足题目要求,然后while条件去处理部满足情况的条件,并对其计数--,遍历下一个直到满足情况。
const int mod=1e9+7;
int beautifulBouquet(vector<int>& flowers, int cnt) {
unordered_map<int,int> dic;
int n=flowers.size();
long long res=0;
for(int left=0,right=0;i<n;i++){
dic[flowers[right]]++;
while(dic[flowers[right]]>cnt) dic[flowers[left++]]--;
res+=i-j+1;
}
return res%mod;
}
提升题目:覆盖最小子串。76. 最小覆盖子串 - 力扣(LeetCode)
思路:定义两个数组,一个函数去判断是否覆盖住,主逻辑里面的函数主要就是,比对长度更新区间。
class Solution {
public:
bool is_coverd(int cnt_s[], int cnt_t[]){
for(int i='A';i<'Z';i++){
if(cnt_s[i]<cnt_t[i])return false;
}
for(int i='a';i<'z';i++){
if(cnt_s[i]<cnt_t[i])return false;
}
return true;
}
string minWindow(string s, string t) {
int n=s.length();
int ans_left=-1;int ans_right=n;
int cnt_s[128]{};
int cnt_t[128]{};
for(char c:t)cnt_t[c]++;
for(int left=0,right=0;right<n;right++){
cnt_s[s[right]]++;
while(is_coverd(cnt_s,cnt_t)){
if(right-left<ans_right-ans_left){
ans_left=left,ans_right=right;
}
cnt_s[s[left++]]--;
}
}
return ans_left<0?"":s.substr(ans_left,ans_right-ans_left+1);
}
};
二、螺旋数组
思路:主要是先定义一个N^2的二维空间,然后初始化变量,主要是对边界条件的限制,必须明确,要不然会导致待出报错。
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
vector<vector<int>>res(n,vector<int>(n,0));
int startx=0,starty=0;
int loop=n/2;//循环走四次
int mid=n/2;//中心位置
int count=1;//给空格赋值
int offset=1;//控制遍历长度
int i,j;
while(loop--){
i=startx;j=starty;
for(j;j<n-offset;j++) res[i][j]=count++;
for(i;i<n-offset;i++) res[i][j]=count++;
for(;j>starty;j--) res[i][j]=count++;
for(;i>startx;i--) res[i][j]=count++;
startx++;starty++;offset+=1;
}
if(n%2) res[mid][mid]=count;
return res;
}
};
三、区间和
思路:开大小和n对应的空间,把之前计算的中间数据累加起来,然后求区间和的时候直接调用辅助空间的数据,进行计算,这样能避免大数据的时间复杂度过高。
#include <iostream>
#include <vector>
using namespace std;
int main() {
int n, a, b;
cin >> n;
vector<int> vec(n);
for (int i = 0; i < n; i++) cin >> vec[i];
while (cin >> a >> b) {
int sum = 0;
// 累加区间 a 到 b 的和
for (int i = a; i <= b; i++) sum += vec[i];
cout << sum << endl;
}
}
上面那个是一般写法,计算更加复杂的场景的时候会超时。
#include<iostream>
#include<vector>
using namespace std;
int main(){
int a,b,n;
cin>>n;
vector<int>vec(n);
vector<int> p(n);
int presum=0;
for(int i=0;i<n;i++){
cin>>vec[i];
presum+=vec[i];
p[i]+=presum;
}
while(cin>>a>>b){
int sum=0;
if(a==0)sum=p[b];
else sum=p[b]-p[a-1];
cout<<sum<<endl;
}
return 0;
}
四、开发商购买土地
思路:定义一个二维数组,根据前缀和的思路去统计横向和纵向。
#include <iostream>
#include <vector>
#include <climits>
using namespace std;
int main () {
int n, m;
cin >> n >> m;
int sum = 0;
vector<vector<int>> vec(n, vector<int>(m, 0)) ;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
cin >> vec[i][j];
sum += vec[i][j];
}
}// 统计横向
vector<int> x(n, 0);
for (int i = 0; i < n; i++) {
for (int j = 0 ; j < m; j++) {
x[i] += vec[i][j];
}
}// 统计纵向
vector<int> y(m , 0);
for (int j = 0; j < m; j++) {
for (int i = 0 ; i < n; i++) {
y[j] += vec[i][j];
}
}
int result = INT_MAX;
int xCut = 0;
for (int i = 0 ; i < n; i++) {
xCut += x[i];
result = min(result, abs(sum - xCut - xCut));
}
int yCut = 0;
for (int j = 0; j < m; j++) {
yCut += y[j];
result = min(result, abs(sum - yCut - yCut));
}
cout << result << endl;
}
总结
在数组的题型中,我们常使用的方法有二分提升查找的效率、双指针提升遍历的效率、滑动窗口提升对一段数据的处理能力、前缀和用空间换时间的效率、同时需要注意边界条件的控制,比如螺旋数组一不小心就会越界,报错,基本熟悉数组的操作,我们对于复杂的应用场景题目处理才能提升!!!共同进步!