力扣日记8.29-【哈希表篇】有效的字母异位词 相关推荐

本文介绍了如何使用哈希表和滑动窗口技巧解决LeetCode上的438题,通过对比字符串s和目标字符串p中字符的出现次数来查找所有异位词的子串起始索引。

力扣日记:【哈希表篇】有效的字母异位词 相关推荐

日期:2023.8.29
参考:代码随想录、力扣

438.找到字符串中所有字母异位词

题目描述

难度:中等

给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。

异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。

示例 1:

输入: s = "cbaebabacd", p = "abc"
输出: [0,6]
解释:
起始索引等于 0 的子串是 "cba", 它是 "abc" 的异位词。
起始索引等于 6 的子串是 "bac", 它是 "abc" 的异位词。

示例 2:

输入: s = "abab", p = "ab"
输出: [0,1,2]
解释:
起始索引等于 0 的子串是 "ab", 它是 "ab" 的异位词。
起始索引等于 1 的子串是 "ba", 它是 "ab" 的异位词。
起始索引等于 2 的子串是 "ab", 它是 "ab" 的异位词。

提示:

  • 1 <= s.length, p.length <= 3 * 10^4
  • s 和 p 仅包含小写字母

题解

class Solution {
public:
    vector<int> findAnagrams(string s, string p) {
        // 时间复杂度:O(m)+O(m)+O(n-m)=O(n+m)
        // 空间复杂度:O(∑)
        vector<int> answer;
        unordered_map<char, int> record;
        unordered_map<char, int> for_check;
        int sLen = s.size();
        int pLen = p.size();
        for (const auto& ch: p) {
            record[ch]++;
            for_check[ch]++;
        }
        int front = 0, end = 0;
        while(end < sLen) { 
            while (end - front < pLen) { // 将end移到对应位置(初始化滑动窗口)
                if (for_check.find(s[end]) != for_check.end()) { // 该字符存在于p中
                    record[s[end]]--;
                    if (record[s[end]] == 0) {
                        record.erase(s[end]); // 通过键删除对应键值对
                    }
                }
                end++;
            }
            // 判断是否是异位词
            if (record.size() == 0) {
                answer.push_back(front);
            }
            if (for_check.find(s[end]) != for_check.end()) { // 该字符存在于p中
                record[s[end]]--;
                if (record[s[end]] == 0) {
                    record.erase(s[end]); // 通过键删除对应键值对
                }
            }
            if (for_check.find(s[front]) != for_check.end()) {
                record[s[front]]++;
                if (record[s[front]] == 0) {
                    record.erase(s[front]);
                }
            }
            front++;
            end++;
        }
        // 判断是否是异位词
        if (record.size() == 0) { // 最后还要检查一次
            answer.push_back(front);
        }
        return answer;
    }
};

复杂度

  1. 哈希表比较+哈希表差值

    时间复杂度::初始化recordfor_check需要O(2m)=O(m),初始化滑动窗口O(m),滑动及判断需要O(3(n-m))=O(n-m),因此需要O(n+m)
    空间复杂度:O(2∑)=O(∑) // ∑为p中字母种类数(需要两个哈希表)

思路总结

  • 使用哈希表虽然复杂度看上去低,但实际执行时间和占用内存会比较大
  • 总体思路是:与p长度相同的滑动窗口依次向右移动,每次判断滑动窗口是否是异位词,是则记录滑动窗口的起始位置。
  • 为了利用哈希表查询快的特性,使用哈希表for_checkrecord分别记录p以及滑动窗口中字母种类和对应个数。
  • 其中记录p的哈希表即for_check,用于判断是否滑动窗口起始或终止位置的字符是否存在于p中,如
    for_check.find(s[end]) != for_check.end()
    或
    for_check.find(s[front]) != for_check.end()
    
    只有该字符存在于p中,才进行哈希表record的更新(for_check在滑动过程中不更新,只用于判断)。
  • 而哈希表record用于判断滑动窗口中的字符是否是p的异位词,其表示的实际上是两者的差值,因此当窗口终止位置(即下一个即将包含进窗口的字符)存在于p中,则record对应字符个数–(差距变小了),而当窗口起始位置(即下一个即将退出窗口的字符)存在于p中,则对应字符个数++(差距变大了)。当record中各个字符个数均为0,则表示为异位词。
  • 更进一步的,为了使得判断是否异位词的复杂度为O(1),使用record.size() == 0进行判断。因此,在每次滑动窗口++或–后若对应字符个数为0,需要进行erase操作。
  • 力扣官方题解 使用了数组进行判断。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值