每日两道力扣,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 {};,能省去不必要的运行开销。

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

相关推荐
手写码匠10 小时前
从零实现 Prompt 工程引擎:结构化提示、自动优化与多轮自省体系
人工智能·深度学习·算法·aigc
无限码力10 小时前
阿里算法岗 0530笔试真题 - 多约束条件下的元素匹配统计
算法·阿里笔试真题·阿里机试真题·阿里算法岗笔试
lqqjuly10 小时前
MLA — 多头潜在注意力深度解析
深度学习·神经网络·算法
吴可可12311 小时前
SolidWorks草图转三维DWG技巧
算法
凡人叶枫11 小时前
Effective C++ 条款04:确定对象被使用前已先被初始化
java·linux·开发语言·c++·嵌入式开发
不想写代码的星星11 小时前
std::move 根本不移动,就像老婆饼里没有老婆
c++
redaijufeng11 小时前
C++雾中风景7:闭包
c++·算法·风景
小欣加油12 小时前
leetcode287寻找重复数
数据结构·c++·算法·leetcode
思麟呀12 小时前
C++11 核心特性(三):强类型枚举、static_assert 与 std::tuple
开发语言·c++
一拳一个呆瓜12 小时前
【STL】C++程序的启动与终止
c++·stl