每日两道力扣,day8

每日两道力扣,day8

每日两道力扣,day8

每日两道力扣,今日是:

3. 无重复字符的最长子串 - 力扣(LeetCode)

438. 找到字符串中所有字母异位词 - 力扣(LeetCode)

第一题:无重复字符的最长子串

3. 无重复字符的最长子串 - 力扣(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] <= targethash[out]-- <= target 这种前置和后置的写法,顺序绝不能错!)

3. 窗口大小的严格控制:

因为寻找的是异位词,异位词的长度绝对不可能变。所以维护窗口大小时,判断条件是 if (当前窗口长度 > p的长度),只要大于,就无条件触发左指针的"出窗口"动作,确保窗口像一个硬纸板框一样,长度永远不变地往右平移。

4. 初始状态的考量:

如果 s 的长度比 p 还要短,那连一个框都塞不下,根本不可能有异位词。虽然现在的滑动窗口代码在逻辑上能兼容这种情况,但在追求极致的面试中,可以在函数开头加上一句 if (s.size() < p.size()) return {};,能省去不必要的运行开销。

好了,今天的每日两道力扣到这里就算是结束了,看完是不是感觉有所收获呢?如果学有所获的话,麻烦给个三连支持一下呗。感谢观看,您的支持,将是我前进路上的重要动力。

相关推荐
CheerWWW2 小时前
C++学习笔记——线程、计时器、多维数组、排序
c++·笔记·学习
无限进步_2 小时前
【C++】验证回文字符串:高效算法详解与优化
java·开发语言·c++·git·算法·github·visual studio
charlie1145141912 小时前
嵌入式现代C++工程实践——第10篇:HAL_GPIO_Init —— 把引脚配置告诉芯片的仪式
开发语言·c++·stm32·单片机·c
呼啦啦5612 小时前
C++动态内存管理
c++
Meme Buoy2 小时前
18.补充数学1:生成树-最短路径-最大流量-线性规划
数据结构·算法
paeamecium2 小时前
【PAT甲级真题】- Count PAT‘s (25)
c++·算法·动态规划·pat考试·pat
汀、人工智能2 小时前
[特殊字符] 第89课:岛屿数量
数据结构·算法·数据库架构·图论·bfs·岛屿数量
九英里路3 小时前
cpp容器——string模拟实现
java·前端·数据结构·c++·算法·容器·字符串
A.A呐3 小时前
【C++第二十七章】C++类型转换
c++