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

L

在 C++ 中,两个 vector<int> 类型的变量进行 == 操作时,会逐个比较它们的元素,只有当两个向量的长度相同且每个位置上的元素值都相同时,== 操作才会返回 true

因此,在这道题的代码中,sCount == pCount 这一操作会执行如下步骤:

  1. 长度比较:首先,两个 vector<int> 的长度会进行比较。如果长度不相等,直接返回 false
  2. 元素逐个比较:如果长度相等,那么会逐个比较两个向量对应位置的元素。如果每个位置上的元素值都相同,则返回 true;如果某个位置的元素不相等,则立即返回 false

在这道题中,sCountpCount 的长度始终固定为 26(对应 26 个小写字母),因此不会出现长度不同的情况。== 操作会逐个比较这 26 个元素的值,当 sCountpCount 相等时,意味着当前窗口中的字符频率与字符串 p 中字符的频率完全一致,即找到了一个异位词。

具体执行流程:

  • 对于每次 sCount == pCount,程序将比较两个 vector 的 26 个位置。
  • 如果所有位置的值都相同,说明这两个频率数组是一样的,那么就返回 true
  • 如果有一个位置的值不同,则返回 false

vector== 操作的时间复杂度:

在这个特定例子中,sCountpCount 的长度固定为 26,所以比较操作是常数时间 O(26),即 O(1)。

class Solution {
public:
    vector<int> findAnagrams(string s, string p) {
        
        vector<int> result;
        //如果s的长度都小于p,那么没有可能的异位词,应该直接返回空结果。
        if (s.size() < p.size()) return result;

        //由于字符串仅仅包含小写字母,所以我们可以创建2个元素个数为26的向量来代替hash表
        //我们可以在这两个向量中统计p的字符频率和s中滑动窗口中的字符频率
        vector<int> pCount(26, 0);
        vector<int> sCount(26, 0);


        //然后分别统计p中的字符频率和第一个滑动窗口中的字符频率
        for(char c:p) {
            pCount[c - 'a']++;
        }

        int windowSize = p.size();

        for(int i = 0; i < windowSize; ++i) {
            sCount[s[i] - 'a']++;
        }

        //如果p的字符频率和第一个窗口中的字符频率相同,则将下标0加入到result数组中。
        if(pCount == sCount) { //当且仅当两个向量中每个位置的每个元素都相同时, == 返回true
            result.push_back(0);
        }

        //然后开始滑动窗口, 这里 i 是滑动窗口的最右下标
        for(int i = windowSize; i < s.size(); ++i) {
            sCount[s[i] - 'a']++;
            sCount[s[i - windowSize] - 'a']--; //之前窗口最左边的元素删除
            if(sCount == pCount) {
                // i - windowSize + 1 是窗口起始下标,
                //因为 i - 起始下标 + 1 = windowSize; 所以窗口起始下标 = i - windowSize + 1
                result.push_back(i - windowSize + 1); 
            }
        }
    return result;
    }
};

算法代码中,最巧妙的地方是通过这两行代码,将字符串p和窗口中的子串每个字符存储在下标相同的数组位置上,这使得可以通过==操作符来判断是否是异位词

pCount[c - 'a']++;

sCount[s[i] - 'a']++;

是的,你理解得非常准确!这两行代码是该算法的关键部分之一,使用了字符的 ASCII 码来将字符映射到数组的正确位置上。

具体来说:

  • pCount[c - 'a']++ 中,c - 'a' 计算的是字符 c 到字符 'a' 的偏移量。由于小写字母的 ASCII 码是连续的(‘a’ 的 ASCII 码最小,其他字母依次递增),所以通过 c - 'a' 可以将字母 'a' 对应到数组的索引 0,字母 'b' 对应到索引 1,依次类推直到 'z' 对应到索引 25。
  • pCountsCount 都是长度为 26 的数组,每个元素表示相应字母的出现频率。例如,pCount[0] 代表字符 'a' 在字符串 p 中出现的次数,pCount[1] 代表字符 'b' 的出现次数,依次类推。
  • 对于 sCount[s[i] - 'a']++,它将字符串 s 中当前窗口的字符同样映射到对应的数组位置,统计每个字符在窗口内出现的次数。

巧妙之处:

  1. 数组映射代替哈希表:因为只有 26 个小写字母,所以我们用一个长度为 26 的数组(vector<int>)来代替哈希表,这样避免了复杂的哈希映射,同时通过数组的下标操作可以快速访问字符对应的位置,节省了时间。

  2. 字符频率比较:通过将两个字符串的字符频率存储在数组中的对应位置(下标),我们可以直接利用 == 操作符逐个比较两个频率数组,判断当前窗口中的子串是否是 p 的异位词。这种方式比使用排序或者逐个字符比较更加高效,特别适合字符种类较少的情况。

总结:

这两行代码的巧妙之处在于将字符直接映射到数组下标,从而可以用数组来记录字符出现的频率,并通过简单的数组比较来判断异位词,从而使得算法简洁、高效。

为什么这一行代码的下标是i - windowSize + 1而不是i - windowSize?

            if(sCount == pCount) {
                // i - windowSize + 1 是窗口起始下标,
                //因为 i - 起始下标 + 1 = windowSize; 所以窗口起始下标 = i - windowSize + 1
                result.push_back(i - windowSize + 1); 
            }

这行代码中的下标 i - windowSize + 1 是用来计算异位词的起始位置。让我们一步步来理解为什么这个下标需要加 1

滑动窗口的工作原理:

假设 p 的长度是 windowSize,每次滑动窗口检查的都是一个长度为 windowSize 的子串。

  • i 为当前窗口的最右端下标时,窗口的左端下标就是 i - windowSize + 1,这样窗口的长度恰好为 windowSize

举个例子:

假设字符串 s = "abab", p = "ab",则 windowSize = 2,我们看看当 i 变化时,窗口的边界情况:

  • i = 1 时,当前窗口的字符是 "ab"s[0]s[1]),它是从 i - windowSize + 1 = 1 - 2 + 1 = 0 开始的。
  • i = 2 时,当前窗口的字符是 "ba"s[1]s[2]),它是从 i - windowSize + 1 = 2 - 2 + 1 = 1 开始的。

可以看到,加上 +1 是为了得到窗口的起始位置,因为 i 是当前窗口的最右端索引,而我们需要将这个窗口的最左端索引加入到结果中。

更详细的解释:

  • i 是当前窗口的 最右端 的索引(即当前我们处理的字符的索引)。
  • windowSize 是窗口的长度,所以窗口的左端应该是 i - windowSize + 1
  • 如果我们直接用 i - windowSize,得到的是窗口左端 之前 的一个位置,不是实际的起始位置。因此,需要加 1 来得到准确的左端起始位置。

总结:

i - windowSize + 1 的作用是从当前窗口的最右端 i 推导出该窗口的 左端起始位置,并将这个起始位置存入结果中。这是因为 i 对应的是当前滑动窗口的最右边界,而我们需要的是这个窗口的最左边界(即异位词的起始位置)。

滑动窗口是一种常用的算法技巧,可以用于解决一类问题,其包括一些LeetCode上的题目。通过维护一个窗口,我们可以在线性时间内解决一些需要处理连续子数组或子字符串的问题。以下是一些常见的滑动窗口问题: 1. 最小覆盖子串(Minimum Window Substring):给定一个字符串S和一个字符串T,在S找出包含T所有字符的最小子串。 2. 字符串的排列(Permutation in String):给定两个字符串s1和s2,判断s2是否包含s1的排列。 3. 找到字符串所有字母异位(Find All Anagrams in a String):给定一个字符串s和一个非空字符串p,找到s所有是p的字母异位的子串。 4. 替换后的最长重复字符(Longest Repeating Character Replacement):给定一个只包含大写英文字母字符串s,你可以将一个字母替换成任意其他字母,使得包含重复字母的最长子串的长度最大化。 5. 至多包含两个不同字符的最长子串(Longest Substring with At Most Two Distinct Characters):给定一个字符串s,找出至多包含两个不同字符的最长子串的长度。 以上只是几个例子,滑动窗口可以应用于更多类型的问题。在解决这些问题时,我们通常使用两个指针来表示窗口的左右边界,并根据具体问题的要求移动窗口。在每次移动窗口时,我们可以更新窗口的状态,例如统计字符出现次数、判断窗口是否满足条件等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值