560.和为K的子数组
题目
我的思路:暴力
但是边界的问题一直有bug
cpp
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
int res=0;
for(int i=0;i<nums.size();i++)
{
int sum=nums[i];
if(sum==k)
{
res++;
// continue;
}
for(int j=i+1;j<nums.size();j++)
{
sum+=nums[j];
if(sum==k)
{
res++;
}
}
}
return res;
}
};
别人的题解
题解
方法一:暴力枚举思路:
- 三重循环:
一层循环枚举做指针left,一层循环枚举right指针,最内层循环遍历left到right之间的连续字串,看是否和为k。 - 两重循环(和我的思路一样,精简了,但是超时了)
cpp
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
int res=0;
for(int i=0;i<nums.size();i++)
{
/* int sum=nums[i];
if(sum==k)
{
res++;
}*/
int sum=0;
for(int j=i;j<nums.size();j++)//这里直接将j=i+1改成j=i,把sum每次都从0开始
{
sum+=nums[j];
if(sum==k)
{
res++;
}
}
}
return res;
}
};
官方的:看[j...i]是否和为k
cpp
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
int res=0;
for(int i=0;i<nums.size();i++)
{
int sum=0;
for(int j=i;j>=0;j--)//[j...i]是否满足和为k
{ sum+=nums[j];
if(sum==k)
{
res++;
}
}
}
return res;
}
};
方法二:前缀和+哈希
本质优化上面的,pre[i]为[0...i]的前缀和,计算[j...i]的和为公式:pre[i]-pre[j-1]==k,即pre[j-1]=pre[i]-k;
我们遍历的时候从左向右进行,使用i进行遍历,用哈希unordered_map<int,int> mp;存储<前缀和''i-k'',前缀和''i-k''出现的次数>,即看是否有pre[j]存在,存在的话说明存在[j...i]的子数组。
【评论区补充】:
前缀和的概念 首先,我们使用一个叫做"前缀和"的概念。对于数组中的任何位置 j,前缀和 pre[j] 是数组中从第一个元素到第 j 个元素的总和。这意味着如果你想知道从元素 i+1 到 j 的子数组的和,你可以用 pre[j] - pre[i] 来计算。
使用 Map 来存储前缀和 在代码中,我们用一个 Map(哈希表)来存储每个前缀和出现的次数。这是为了快速检查某个特定的前缀和是否已经存在,以及它出现了多少次。
核心逻辑解析 当我们在数组中向前移动时,我们逐步增加 pre(当前的累积和)。对于每个新的 pre 值,我们检查 pre - k 是否在 Map 中。
cpp
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
int res=0,pre=0;
unordered_map<int,int> mp;
mp[0]=1; //刚开始第一个前缀出现的次数为1
for(int i=0;i<nums.size();i++)
{
pre+=nums[i];
if(mp.find(pre-k)!=mp.end())
{
res+=mp[pre-k];
}
mp[pre]++; //先查询后再将当前pre存储
}
return res;
}
};
239. 滑动窗口最大值
这个题目和上面的题目有点类似,都是三重循环,一层是总的遍历数量,一层left,一层right遍历left到right之间的(k长度)
方法一:暴力超时了
方法二:优先队列。
对方法一的内层循环降低复杂度,其实通过priority_queue这个数据结构来找最大值,双指针还是两重循环,只是优先队列底层是堆,复杂度是O(logn)
方法三:单调队列
双指针i,j也可以是双端队列,左边i,右边j。单调队列用双端队列来实现。在二的基础上又降低了一层复杂度。
cpp
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
deque<int> q;
//单调递减
for(int i=0;i<k;i++)
{
while(!q.empty()&&nums[i]>=nums[q.back()])
{
q.pop_back();
}
q.push_back(i);
}
vector<int> res={nums[q.front()]};
for(int i=k;i<nums.size();i++)
{
while(!q.empty()&&nums[i]>=nums[q.back()])
{
q.pop_back();
}
q.push_back(i);
while(q.front()<=i-k)
{
q.pop_front();
}
res.push_back(nums[q.front()]);
}
return res;
}
};
76. 最小覆盖字串
没有思路,不知道怎么判断滑动窗口里的字串字母个数是否小于等于t里的字母个数
这里官方用的哈希unordered_map,没有用vector v(26)
下面的评论区的解:FearnDreams看懂了:
- 使用一个单独的函数检查是否滑动窗口里的和s里的个数大于等于
- 检查的时机:当滑动窗口里新加入的字符在t里的时候就检查
- 注意使用s.substr(startpos,len);来截取,而不是用一个res变量保存每次最小的,所以用了res_left来保存每次滑动窗口最左边的位置。
cpp
class Solution {
public:
//定义两个哈希表,tstr用来存放t中元素的出现次数信息,sstr用来存放滑动窗口中元素的出现次数信息
unordered_map<char,int> tstr,sstr;
//检查当前的窗口是否是合格的窗口,即:
//检查当前滑动窗口中元素是否完全覆盖了字符串t中的所有元素(重点是某元素的出现次数必须不小于t中对应元素的出现次数)
bool check()
{
for(auto tchar : tstr)
{
if(tchar.second > sstr[tchar.first]) return false;//注意这里的判断条件是大于
//只要sstr中元素的second值不小于tchar中对应元素的second值就行
}
return true;
}
string minWindow(string s, string t) {
int n1 = s.size(),n2 = t.size();
if(n1<n2) return "";//如果t串比s串还长,s中肯定不能找到相应的子串
int len = INT_MAX;//最小窗口的长度
int ans_left = -1;//最小窗口的左边界指针
//构造哈希表
for(auto tchar : t)
++tstr[tchar];
int left = 0,right = 0;//窗口的左右两端指针
//滑动窗口右端指针遍历整个s串
for(int right = 0;right<n1;right++)
{
//每遍历一个元素,更新sstr中的元素信息
++sstr[s[right]];
//如果当前遍历到的元素在tstr中也有,说明此次遍历的更新是有效的更新,否则不用管,直接继续遍历
if(tstr.find(s[right]) != tstr.end())
{
//对于每一次有效的更新,检查当前窗口中的子串是否是一个合格的子串
while(check() && left<=right)
{
//如果当前子串是合格的,那么判断是否是最小的窗口
if(len > right - left +1)
{
//如果是最小的窗口,那么更新窗口信息
ans_left = left;
len = right - left + 1;
}
//当前子串如果是合格的,那么尝试移进窗口的左边界缩短窗口的长度
--sstr[s[left]];//窗口左边界的元素信息从哈希表sstr中删除
left++;//移进窗口左边界
//移进之后,继续while判断移进后的子串是否是合格的,如果是合格的,继续重复同样的操作,更新窗口的信息
}
//一旦窗口不合格,窗口右边界的指针就继续往后遍历,拓宽窗口的长度
}
}
if(ans_left == -1) return "";
else return s.substr(ans_left,len);
}
};