前序:相关题目与思路来自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;
}