438.找到字符串中所有字母异位词
思路:这道题显然是一道滑动窗口匹配的题目,可以先创建一个pMap用于存储p字符串(原子串)及其对应的字符出现的频率。然后使用sMap维持一个字符个数为p.size()长度的存有s部分子串出现频率映射的哈希表。然后就通过,窗口不断滑动,更新,与比较,如果相同则保存相同位置的起始点进入最终的vector即可。
// @lc code=start
#include<vector>
#include<unordered_map>
#include<string>
using namespace std;
class Solution {
public:
vector<int> findAnagrams(string s, string p) {
vector<int> ans;
int pLength = p.size();
int sLength = s.size();
if(pLength>sLength) return ans;
// 使用pMap记录比较窗口
unordered_map<char,int> pMap;
for(char c:p){
pMap[c]++;
}
// 使用sMap记录当前的滑动窗口
unordered_map<char,int> sMap;
for(int i=0;i<pLength;i++){
sMap[s[i]]++;
}
// 如果第一个滑动窗口就相等则记录
if(pMap == sMap){
ans.push_back(0);
}
// 开始滑动
for(int i=pLength;i<sLength;i++){
sMap[s[i]]++;
sMap[s[i-pLength]]--;
if(sMap[s[i-pLength]]==0) sMap.erase(s[i-pLength]);
if(sMap==pMap){
ans.push_back(i-pLength+1);
}
}
return ans;
}
};
时间复杂度:O(n),n取决于s的长度
空间复杂度:O(m),m取决于p的长度
560.和为k的子数组
思路(错误):考虑使用前缀和以及双指针,分别维持左右两个指针l和r,初始化为0和1,然后维持一个sum初始化为nums[0]+nums[1],做一些初值特判和防止数组越界的操作。在循环中不断比较当前的sum与k的大小关系,如果sum<k,则说明当前l到r范围内的和太小,则是r指针右移(注意越界问题),更新sum。如果sum>k,则说明当前l到r范围内的和太大,则使l指针右移。若相等(比较关键),则是l指针右移,因为我们无法排除当前的nums【l】==0的情况,所以不能让l和r同时右移(最初想法),并且是ans+1。
不过这种思路没过,因为我没有考虑到数组元素是负数的情况。后面加了一些判断还是有问题,就换了第二种思路。
问题代码:
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
int l=0,r=1;
int ans=0;
if(nums[l]==k) ans++;
if(nums.size()==1){
return ans;
}
int sum = nums[l]+nums[r];
while(l<nums.size()){
if(l==r) sum = nums[l];
if(sum>k){
sum -= nums[l];
l++;
}else if(sum<k&&nums[l]>0){
if(r<nums.size()) r++;
else return ans;
sum = sum + nums[r];
}else if(sum<k&&nums[l]<0){
sum = sum - nums[l];
l++;
}
else{
ans++;
l++;
sum = sum- nums[l];
}
}
return ans;
}
};
解法2:GPT给出的前缀和+map映射(很牛的方法,可以好好学习)
详细解释
-
初始化前缀和:
currentSum
用于存储当前的前缀和,初始值为 0。- 在每次循环中,我们通过
currentSum += num;
更新当前的前缀和。这个操作的意义是将当前元素的值加入到前缀和中。
-
前缀和的定义:
- 前缀和是数组中从起始位置到当前位置的所有元素的和。例如,对于数组
[1, 2, 3]
,前缀和的计算过程如下:- 第一个元素:
currentSum = 1
- 第二个元素:
currentSum = 1 + 2 = 3
- 第三个元素:
currentSum = 1 + 2 + 3 = 6
- 第一个元素:
- 前缀和是数组中从起始位置到当前位置的所有元素的和。例如,对于数组
-
查找条件:
currentSum - k
的计算是为了找到一个之前的前缀和,使得从这个前缀和到当前元素的子数组的和为 k。- 如果
prefixSumCount
中存在currentSum - k
,这意味着从某个位置到当前元素之间的子数组和正好等于 k。
-
更新答案:
ans += prefixSumCount[currentSum - k];
:如果找到这样的前缀和,说明有多个子数组的和为 kk。我们将这些子数组的数量加到ans
中。- 这里的
prefixSumCount[currentSum - k]
表示之前有多少次出现过这样的前缀和。
-
更新前缀和的出现次数:
prefixSumCount[currentSum]++;
:在每次循环结束时,我们将当前前缀和的出现次数加 1。这是因为当前的currentSum
现在也可以作为一个前缀和,用于后续的计算。- 例如,如果当前
currentSum
是 6,那么在后续的循环中,如果有其他元素使得currentSum
再次变为 6,我们就能利用之前的出现次数来计算更多的子数组。
整体流程示例
假设我们有一个数组 nums = [1, 1, 1]
,并且 k=2k=2。
-
初始状态:
prefixSumCount
初始化为{0: 1}
(表示前缀和为 0 出现 1 次)。currentSum = 0
,ans = 0
.
-
遍历过程:
-
第一次循环(
num = 1
):currentSum = 0 + 1 = 1
- 查找
1 - 2 = -1
,未找到,ans
仍为 0。 - 更新
prefixSumCount
为{0: 1, 1: 1}
。
-
第二次循环(
num = 1
):currentSum = 1 + 1 = 2
- 查找
2 - 2 = 0
,找到,ans += prefixSumCount[0]
,ans
变为 1。 - 更新
prefixSumCount
为{0: 1, 1: 1, 2: 1}
。
-
第三次循环(
num = 1
):currentSum = 2 + 1 = 3
- 查找
3 - 2 = 1
,找到,ans += prefixSumCount[1]
,ans
变为 2。 - 更新
prefixSumCount
为{0: 1, 1: 1, 2: 1, 3: 1}
。
-
-
结果:
- 最终
ans
的值为 2,表示数组中有两个子数组和为 kk(即[1, 1]
和[1, 1]
)。
- 最终
总结
通过前缀和的概念,我们能够在一次遍历中有效地计算出和为 kk 的子数组数量。哈希表 prefixSumCount
用于存储每个前缀和的出现次数,使得我们能够快速查找并更新结果。这个方法的高效性在于它避免了使用双重循环的 O(n2)O(n2) 复杂度,将其降低到 O(n)O(n)。
#include <vector>
#include <unordered_map>
using namespace std;
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
unordered_map<int, int> prefixSumCount;
prefixSumCount[0] = 1; // 处理和为 k 的情况
int currentSum = 0;
int ans = 0;
for (int num : nums) {
currentSum += num; // 更新当前前缀和
// 检查是否存在前缀和为 currentSum - k 的情况
if (prefixSumCount.find(currentSum - k) != prefixSumCount.end()) {
ans += prefixSumCount[currentSum - k];
}
// 更新当前前缀和的计数
prefixSumCount[currentSum]++;
}
return ans;
}
};