每日两道力扣,day8
每日两道力扣,day8

每日两道力扣,今日是:
438. 找到字符串中所有字母异位词 - 力扣(LeetCode)
第一题:无重复字符的最长子串


1.思路:
首先s由英文字母、数字、符号和空格组成,所以我们可以知道总共有128个字符,咱们定义一个int类型的hash数组,数组大小为128,初始化数组为0。
看到这种跟子串有关的题目,我们应该条件反射,立马想到滑动窗口算法,我称之毛毛虫算法。像一条毛毛虫一样,先蠕动右边,到达零界点,再移动左边。遍历完数组后,得到答案。
因为我们已经定义了一个hash数组用来记录窗口内每个字符的出现次数,剩下的只需要按进窗口------出窗口------更新结果这"三部曲"走。
具体实现见代码。
2.代码实现:
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int hash[128] = {0};
int left = 0, right = 0;
int len = 0;
//进窗口
while(right < s.size())
{
char in = s[right];
hash[in]++;
//出窗口
while(hash[in] > 1)
{
char out = s[left];
hash[out]--;
left++;
}
//更新结果
len = max(len,right - left + 1);
right++;
}
return len;
}
};
3.细节:
在这类题中,尤其要注意进窗口,出窗口,更新结果的时机。
第二题:找到字符串所以字母的异位词


1.思路:
一、 核心解题思路:定长滑动窗口 + 极速校验
这道题的本质是:在一个长字符串 s 中,拿一个固定大小的框(长度等于 p 的长度),从左到右去套,看框住的字符是不是和 p 的字符构成完全一样。
(1)制定"通关配额"(哈希表统计): 因为全是小写字母,我们直接开一个大小为 26 的数组作为哈希表,先统计目标字符串 p 中每个字符需要的数量。这就是我们的"通关配额"。
(2)引入灵魂变量 count(有效匹配数): 如果我们每次框住一段字符,都去循环遍历比对两个哈希表是不是长得一样,那就太笨重了。 神级操作是引入一个变量 count,专门记录=="当前框里,有几个字符是有效且被需要的==。 只要 count 的数量正好等于 p 的长度,说明框里全是"宝",且数量不多不少刚刚好,直接收集答案!
(3)右边吃,左边拉(滑动窗口运转):
- 进窗口(吃): 右指针往右走,吃进一个字符。如果这个字符在框里的数量 小于等于 配额里的数量,说明它是"被需要的",
count增加。 - 出窗口(拉): 当框的长度超过了
p的长度,左指针必须往右走,吐出一个字符。如果吐出前,这个字符在框里的数量 小于等于 配额数量,说明我们"吐掉了一块宝",count减少。 - 收集答案: 每次出入操作完成后,看一眼
count是否等于p的长度,等于就记录左指针的位置。
2.代码实现:
class Solution
{
public:
vector<int> findAnagrams(string s, string p)
{
vector<int> ret;
int hash1[26] = { 0 }; // 统计字符串 p 中每个字符出现的个数
for(auto ch : p) hash1[ch - 'a']++;
int hash2[26] = { 0 }; // 统计窗⼝⾥⾯的每⼀个字符出现的个数
int m = p.size();
for(int left = 0, right = 0, count = 0; right < s.size(); right++)
{
char in = s[right];
// 进窗⼝ + 维护 count
if(++hash2[in - 'a'] <= hash1[in - 'a']) count++;
if(right - left + 1 > m) // 判断
{
char out = s[left++];
// 出窗⼝ + 维护 count
if(hash2[out - 'a']-- <= hash1[out - 'a']) count--;
}
// 更新结果
if(count == m) ret.push_back(left);
}
return ret;
}
};
3.细节:
在手撕代码时,这几个细节极其容易让程序崩溃或报错:
1. 字符映射到数组下标(越界警告):
千万不要直接拿字符去当数组下标!题目说了是纯小写字母,一定要用 当前字符 - 'a' 来映射到 0 到 25 的索引上。如果不减去 'a',直接用 ASCII 码访问大小只有 26 的数组,会瞬间内存越界。
2. count 增减的严密逻辑(不要乱加减):
-
进窗口时: 是先 把框里的字符计数 +1,再 和配额比较。如果 ≤ \le ≤ 配额,才算有效,
count++。如果是超额的废料,框里计数增加,但count绝不能加。 -
出窗口时: 是先 把当前的框里计数和配额比较,如果是 ≤ \le ≤ 配额,说明要掉肉了,
count--。比较完之后,再把框里的字符计数 -1。(这也就是为什么优秀的代码里会用
++hash[in] <= target和hash[out]-- <= target这种前置和后置的写法,顺序绝不能错!)
3. 窗口大小的严格控制:
因为寻找的是异位词,异位词的长度绝对不可能变。所以维护窗口大小时,判断条件是 if (当前窗口长度 > p的长度),只要大于,就无条件触发左指针的"出窗口"动作,确保窗口像一个硬纸板框一样,长度永远不变地往右平移。
4. 初始状态的考量:
如果 s 的长度比 p 还要短,那连一个框都塞不下,根本不可能有异位词。虽然现在的滑动窗口代码在逻辑上能兼容这种情况,但在追求极致的面试中,可以在函数开头加上一句 if (s.size() < p.size()) return {};,能省去不必要的运行开销。
好了,今天的每日两道力扣到这里就算是结束了,看完是不是感觉有所收获呢?如果学有所获的话,麻烦给个三连支持一下呗。感谢观看,您的支持,将是我前进路上的重要动力。