算法之滑动窗口

前序:相关题目与思路来自b站左程云与算法竞赛,个人笔记,如有错误,感谢指出!

总结:

题目分享:

思路:以i位置结尾大于目标和的最小子长度,运用双指针来收缩区间,维护最小长度。根据题目自行调整数据量。

#include<iostream>
#include<algorithm>

using namespace std;

const int N =  1e5 + 10;
int num[N],len,aims;
int ans = 1e9;
int main()
{
    cin >> aims >> len;
	for(int i = 0 ; i < len ; i++)
	cin >> num[i];
	
	int l = 0 , sum = 0;
	for(int r = 0; r < len ; r++)//先移动右指针,先往右扩扩。 
	{
	    sum = sum + num[r];
		while(sum - num[l] >= aims)//看左边能不能向右扩 
		{
			sum = sum - num[l];
			l++; 
		}
		
		if(sum >= aims)//不能扩直接计算长度与之前的相比即可 
		{
		   ans = min(ans , r - l + 1);
		}	
	}
	
	if(ans == 1e9)
	cout<<0<<endl;
	else
	cout << ans << endl;// ans  == 1e9 ? 0 : ans; 
	return 0;	
	
}

题目二:思路分析:遇到重复字符时如何处理遇到重复字符的边界,找到重复字符上一次出现的位置与L位置进行相比较,也有点动态规划的感觉emm。字符与数字的映射,常用的小技巧。

#include<iostream>
#include<algorithm>
#include<string>
#include <cstring>

using namespace std;

int ans = 0;
string s;
int pos[256];//记录上次字符出现的位置。字符映射成相应的下标 pos[a] = pos[0]; 



int main()
{
   cin >> s;
   int len = s.length();
   memset(pos ,-1 , sizeof(pos));
   int l = 0;
   for(int r = 0 ; r < len ; r ++)
   {
   	    l = max(l,pos[s[r]] + 1); //s[r]代表的是对应的字符
		ans = max(ans , r - l + 1);
		pos[s[r]] = r;//更新重复字符的新位置    
   	    
   }
   
   cout << ans << endl; 
   return 0;
}

题目三 :

思路分析:比较巧妙的设成负数,维持0状态就是最好,字符数量大于0在有效长度内就可以缩减,感觉太难想代码比较容易理解,积累经验了害。

#include<iostream>
#include<algorithm>
#include<string>
#include <cstring>

using namespace std;

string s,t;
int cnts[256];//记录每个字符出现的次数,数组下标对应相应的字符

string check(string str,string tar)
{
    if(str.size() < tar.size())
        return "";

    for(int i = 0 ; i < tar.length() ; i++)
    {
        cnts[tar[i]]--;//把目标串出现的次数设置成负数 
    }

    int len = 1e9;
    int start = 0;//从哪个位置开始,发现可以得到最小覆盖字串

    int l = 0, sum = tar.length();
    for(int r = 0 ; r < str.length() ; r ++)
    {
        if(cnts[str[r]]++ < 0)//找到了覆盖字串中的字符 
        {
            sum--;//总数量减减
        }

        if(sum == 0) //全寻到了 
        {
            while(cnts[str[l]] > 0)//能否往右边扩 
            {
                cnts[str[l]]--;
                l++;
            }

            if(r - l + 1 < len)
            {
                len = r - l + 1;
                start = l;
            } 
        }
    }
    return len == 1e9? "" : str.substr(start, len);
}

int main()
{
    cin >> s >> t;
    string ans = check(s,t);
    cout << ans << endl;
    return 0; 
}

题目四:

分析:这个很容易想到作差 如果为正值代表可以通往下一个车站,难处理的是窗口从最右端怎么开始滑动的边界处理。取模的巧妙,不知道咋想,小技巧又要积累太菜了

#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;

const int N = 1e6 + 10;
int cost[N],gas[N];

int solve()
{
	int n;
	cin >> n;
   	for(int i =  0 ; i < n ; i++)
   	cin >> gas[i] >> cost[i];
   	
  
	int r = 0 , len = 0 ,sum = 0;//都用了长度来控制窗口走向 
	
	for(int l = 0 ; l < n ; l++)
	{
		while(sum >= 0)//当前窗口大于0,能往前扩 
		{
			if(len == n)//进来窗口的数量已经等于整个数组长,说明可以绕一圈,返回下标 
			{
				return l;
			}
			
			r = (l + (len++)) % n;//以后对边界没思路想想几个变量之间的关系qwq
			sum = sum + (gas[r] - cost[r]); 
		}
		
		//sum < 0,某个位置不太行;退出窗口 
		len--;
		sum = sum - (gas[l] - cost[l]);
	}
	return -1; 
}


int main()
{
	
   int pos = solve();
   cout << pos << endl;;
   return 0;
   	
}

题目五:

思路:先进行映射常用技巧,后半句有点难理解,如果在待替换子串之外的任意字符的出现次数超过 m=n / 4​,那么无论怎么替换,都无法使这个字符在整个字符串中的出现次数为 m。左神举得例子如下,假设可操作字符的长度为10,窗口之外q为20,w18,e16,r16,这样窗口内10个字符只要全变成wer即可,假设q为22,可操作字符的长度此刻为22,那么wer有一个就不能达到m的数量。

#include<iostream>
#include<algorithm>
#include<string>

using namespace std;

const int N = 1e5 + 10;
int cnt[4];//记录出现的次数
int arr[N];//收集字符

bool judge(int num[], int len, int tar)
{
    for (int i = 0; i < 4; i++)
    {
        
        if(num[i] > tar)
        {
        	return false;
		}
        len = len - (tar - num[i]);//tar - num[i]我要补的数量,再总可支配的减去已经补的 
    }
    return len == 0;
}

int main()
{
    string s;
    cin >> s;
    int len = s.size();
    for (int i = 0; i < len; i++)
    {
        if (s[i] == 'Q')
        {
            arr[i] = 0;
            cnt[arr[i]]++;
        }
        else if (s[i] == 'W')
        {
            arr[i] = 1;
            cnt[arr[i]]++;
        }
        else if (s[i] == 'E')
        {
            arr[i] = 2;
            cnt[arr[i]]++;
        }
        else if (s[i] == 'R')
        {
            arr[i] = 3;
            cnt[arr[i]]++;
        }
    }

    int aims = len / 4;//字符出现的次数
    int ans = len;//basecase,最差的解就是需要全长字符都拿来才能替换成功

    //左闭右开。 
    int r = 0;
    for (int l = 0; l < len; l++)
    {
        // 讨论的 l - r -1 是否可行
        while (!judge(cnt, r - l, aims) && r < len)
        {
            // 窗口之外的统计,进来一个窗口,窗口外对应的就要减小咯 
            cnt[arr[r++]]--;
        }
        // 1) l...r-1 [l,r) ,做到了!
        // 2) r == n,也没做到

        if (judge(cnt, r - l, aims))
        {
            ans = min(ans, r - l);
        }

        cnt[arr[l]]++;//窗口滑动,对应的窗口之外的数量得重新加上 
    }
    cout << ans << endl;
    return 0;
}

题目六:

分析:感觉也挺难想的继续积累经验。直接看代码思路吧

#include<iostream>
#include<algorithm>
#include<string>
#include<cstring>
 
using namespace std;

const int N = 2e5 + 10;

int cnt[N];//记录数量 
int arr[N];
int n,k1;//收集的数字种类 

int solve(int a[], int k) 
{
	memset(cnt , 0 ,sizeof(cnt));
    int l = 0, ans = 0, typeCount = 0;
    for (int r = 0; r < n; r++) {
        if (++cnt[a[r]] == 1) { // 如果这个数字是新出现的,种类数加一
            typeCount++;
        }

        // 当种类数超过k时,移动左端点
        while (typeCount > k) {
            if (--cnt[a[l]] == 0) { // 如果这个数字在数组中不再出现,种类数减一
                typeCount--;
            }
            l++; // 移动左端点
        }

        // 计算满足条件的子数组个数
        ans += (r - l + 1); // 每个子数组至少有一个可能 03 13 23 33..
    }
    return ans;
}

int main()
{
  	
   cin >> n >> k1;
   for(int i = 0 ; i < n ; i++)
   {
       cin >> arr[i];	
   }
   int res = solve(arr , k1)  - solve(arr , k1 - 1);
   cout << res << endl;
   return 0;
  	
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值