1 560. 和为 K 的子数组不能用滑动窗口!
思路:
(1)初步思路:滑动窗口,两个指针 l,r 维护一个试探序列 当右边新加入的数,正好使得序列和为k,ans++,sum = 0 ,l++,r不变。如果新加入的数太大,就开始左端点收缩,直到 序列和 <= k,如果新加如的数太小就继续往后加。但是这个思路的bug——但是过不了[-1,-1,1]之类带负数的。因为,数组元素是既有正数又有负数的,且要求是连续的子数组。数组元素既有正数又有负数,即数组不是单调的。因此不适合用滑动窗口的方法。**滑窗特点是,固定了左边界以后,右边界找到了一个可行解以后,右边界再靠右边的更大的区间肯定不存在目标值,所以才将左边界继续向右滑。**在这道题里显然没有这个性质。
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
// 连续性质——滑动窗口? 一个和小于等于k的窗口 当新加入一个数
// 如果大于左边出 出到窗口sum小于等于k
int l = 0;
int r = 0;
int sum = nums[0];
int ans = 0;
if(sum == k)ans++;
for(int i = 1; i < nums.size();i++)
{
int tmp = sum + nums[i];
if(tmp < k)
{
r++;
sum = tmp;
}
else if(tmp == k)
{
ans++;
r++;
sum = tmp;
}
else
{
r++;
sum = tmp;
while(sum > k && l <= r && r < nums.size())
{
sum -= nums[l];
l++;
}
if(sum == k)ans++;
}
}
return ans;
}
};
(2)其实是前缀和的板子题 但没有tag,我就完全忘了前缀和这件事。这种子区间和需要条件反射想到前缀和。
尝试只用前缀和:但是TLE,1e4的数据范围不能能O(n^2)的算法 1e3的才可以用。
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
vector<int> qian(nums.size()+10);
qian[0] = nums[0];
for(int i = 1; i < nums.size();i++)
qian[i] = qian[i-1] + nums[i];
int ans = 0;
for(int i = 0; i < nums.size();i++) //左端点
{
for(int j = i; j < nums.size();j++)// 右端点
{
int tmp = qian[j] - qian[i] + nums[i];
if(tmp == k)ans++;
}
}
return ans;
}
};
优化:hash表
(学习了别人的题解)其实我们不关心具体是哪两项的前缀和之差等于k,只关心等于 k 的前缀和之差出现的次数c,就知道了有c个子数组求和等于k。
1构造前缀和:遍历 nums 数组,求每一项的前缀和,统计对应的出现次数,以键值对存入 map。
2核心遍历:边存边查看 map,如果 map 中存在 key 为「当前前缀和 - k」,说明这个之前出现的前缀和,满足「当前前缀和 - 该前缀和 == k」,它出现的次数,累加给 count。
如果先把map存好遍历map有一些情况要特殊考虑不好写。我也不知道为啥不好写 错在哪 反正很多错
先加入mma[0] = 1; // 原因是:如果某段前缀和prefix_sum(i)为 k,则prefix_sum - k == 0,所以需要提前往哈希表中存入前缀和0,其频数初始化为1。即这里因为某个子区间和直接用map[j]-map[i] 没有加上num[i] 所以需要考虑上 i = 0的情况。即只需要map[j] 就能等于k的情况,即需要加上mma[0]=1。
AC代码:
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
map<int,int> mma; // key存每一项前缀和值 val是次数
mma[0] = 1; // 原因是:如果某段前缀和prefix_sum(i)为 k,则prefix_sum - k == 0,所以需要提前往哈希表中存入前缀和0,其频数初始化为1
int ans = 0;
int sum = 0;
// 遍历map 不好写
for(auto num : nums)
{
sum += num;
if(mma.count(sum - k))ans+= mma[sum-k];
mma[sum]++;
}
return ans;
}
};
2 239. 滑动窗口最大值
之前做过,这次二刷想到了左边界存子序列最大值的队列,但是每次左边最大退出时候,找新的最大时候要重新遍历一次队列,TLE了,看了题解忘了这题要用单调队列,即保持队列中是单调减小的。写的时候经常有时候糊里糊涂了。脑子跟不上我写的front,还是back的逻辑。
AC代码:
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
// 一个思路: 维护一个窗口左边界是当前小于等于k长度的子序列的最大值 优化==》 单调队列
vector<int> ans;
deque<int> q; // 存下标
for(int t = 0; t <= k-1 ; t++)
{
if(q.empty())q.push_back(t);
else{
while(!q.empty() && nums[t] > nums[q.back()])q.pop_back();
q.push_back(t);
}
}
ans.push_back(nums[q.front()]);
for(int j = k; j< nums.size();j++)
{
while(!q.empty() && nums[j] > nums[q.back()])q.pop_back();
q.push_back(j);
if((j - q.front()+1) > k)q.pop_front();
ans.push_back(nums[q.front()]);
}
return ans;
}
};
3 76. 最小覆盖子串
有点难。
初始思路:
// 典型的滑动窗口思路,窗口扩展时寻找可行解,窗口收缩时优化可行解
// 思路:一个unordered_map装着t的key和对应val
// s采用滑动窗口策略 当新加入一个 对应map-key的val--,num++
// 当map里面没有val是大于0的key的时候,即,找到了一个合适的答案
// 左边回收 收到最小还能满足 map里面没有val是大于0的key
// 更新长度和当前最优串!
// 然后 左边界i++,对应val++ 然后重复右边前进
但是有个问题就是 经常要找map里面没有val是大于0的key,那时间复杂度可能就是O((m+n)*52)。
这样写的代码:
思路正确,但是过不了第265个点
代码:
class Solution {
public:
string minWindow(string s, string t)
{
// 典型的滑动窗口思路,窗口扩展时寻找可行解,窗口收缩时优化可行解
// 思路:一个unordered_map装着t的key和对应val
// s采用滑动窗口策略 当新加入一个 对应map-key的val--
// 当map里面没有val是大于0的key的时候,即,找到了一个合适的答案
// 左边回收 收到最小还能满足 map里面没有val是大于0的key
// 更新长度和当前最优串!
// 然后 左边界i++,对应val++ 然后重复右边前进
unordered_map<char,int> need;
int cnt = 0;
for(char u : t)
{
need[u]++;
cnt++;
}
string bestans;
int bestnum = s.size()+100;
deque<char> now;
int okans = 0;
for(int r = 0; r < s.size();r++) //扩大右边界
{
now.push_back(s[r]);
if(need.count(s[r]))
{
if(need[s[r]] > 0)cnt--;
need[s[r]]--;
}
// 判断一下找齐没有
int ok;
if(cnt == 0)ok = 1;
else ok = 0;
if(!ok)continue;
else
{
okans = 1;
char tmp;
// 进到这里一开始ok都是1即cnt=0
while(ok)
{
tmp = now.front();
now.pop_front();
if(need.count(tmp))
{
need[tmp]++;
if(need[tmp] > 0)cnt++;
}
if(cnt == 0)ok = 1;
else ok = 0;
}
now.push_front(tmp); //复原
// 更新
int num = 0;
string tmp1 = "";
for(auto y:now)
{
num++;
tmp1 += y;
}
cout << tmp1 << endl;
if(num < bestnum)
{
bestnum = num;
bestans = tmp1;
}
now.pop_front();
}
}
if(!okans)return "";
else return bestans;
}
};
总结:
1. 连续子序列之和要条件发射==> 前缀和。
2. 哈希优化。
3. 单调队列