LeetCode 438 Find All Anagrams in a String 题解

LeetCode 438 Find All Anagrams in a String 题解 - 滑动窗口

题意

给出一个字符串 s 和一个非空字符串 p,找出 s 中所有 p 的 anagram 的下标。输入的字符串中只包含小写字母。

anagram:任意改变原字符串中字符顺序的所有新字符串,包括原串。例如 abc, bac, acb, bca 都是 abc 的 anagram。

思路

  1. 特点

    不难看出这是一个搜索子串(substring)的问题,只是这些子串比较特殊 —— 是一个子串通过变换字符位置生成的(这不废话吗)。那么我们就可以得到下面这个结论:

    如果找到了一个长度和 p 相同的子串 t,并且可以确定,p 中出现的每个字符(包括重复的)t 中都出现,那么 t 就是 p 的一个 anagram。

    依照这个思想,我们可以只保留 长度与 p 相同的子串 t 的信息,每次向前移动一个字符,将 t 的尾部字符去掉,将当前字符加入子串 t,然后检查 t 是否为 p 的一个 anagram,重复这个步骤直到 s 末尾。

  2. 滑动窗口

    滑动窗口其实就像乘火车过安检时候的机器,不停地检查乘客的包裹。 其思想不难理解,不过对于这道题,我们不是让传送带一个一个来传送包包,而是让安检机器(窗口)沿着传送带(数据列表)移动来检查每个包包(数据)。

  3. 滑动窗口协议 (Sliding window protocol)

    作为TCP协议中很重要的一个协议,滑动窗口协议是这种思想的一种典型应用,具体细节感兴趣的可以参考计算机网络相关书籍。

    滑动窗口协议(Sliding Window Protocol),属于TCP协议的一种应用,用于网络数据传输时的流量控制,以避免拥塞的发生。该协议允许发送方在停止并等待确认前发送多个数据分组。由于发送方不必每发一个分组就停下来等待确认,因此该协议可以加速数据的传输,提高网络吞吐量。 —— 百度百科

解法

由于 p的 anagrams 中每个字符串包含的字符是一样的,因此,我们可以通过判断当前出现的字符有多少个与 p 中的每个字符相对应,来确定当前子串是否为 p 的 anagram。具体一点,假设 p 为 aab,s 为 abaaa:

  • 我们先统计得到,p 的长度为 plen = 3,p 中字符 a 出现的次 times[a] = 2, b 出现的次数 times[b] = 1,其余字符未出现过。
  • 然后我们拷贝得到一份 tmptimes[] = times[] 和 tmpplen = plen 用来保存 s 中的字符与 p 中字符的抵消记录。从前往后查看 s 中字符:
  • 第一个 a 在 p中出现过,那么抵消一个,将 tmpplen 减一,tmptimes[a]减一;
  • 第二个 b 在 p中出现过,则抵消一个,tmpplen减一,tmptimes[b]减一;
  • 第三个 a,依旧是 a,前面抵消了一个 a,再抵消一个,将 tmpplen 减一,tmptimes[a]减一,这个时候 tmpplen 为 0 了,那么我们可以确定,得到了一个 p 的 anagram;
  • 第四个 a,依旧是 a,由于我们窗口的长度为 3,因此先将第一个的抵消记录清除,tmpplen 加一,tmptimes[a]加一,然后再用这个 a 抵消 p 中的 a,tmpplen 减一,tmptimes[a]减一;tmpplen 为 0,那么这就又是一个 anagram;
  • 第五个 a,依旧是 a,同样,窗口长度为3,因此将第二个 b 的抵消记录清除,tmpplen 加一,tmptimes[b]加一,然后,由于 p 中的 a 已经全部被抵消了(tmptimes[a] = 0),所以 a 不能被抵消,而tmpplen也不为0,因此这个子串 —— aaa 并不是 p 的一个 anagrams。
  • s没有更多字符,结束。

代码

class Solution {
public:
    vector<int> findAnagrams(string s, string p) {
        const int N = 26;
        // 保存 p 中每个字符是否出现,出现次数,以及总长度
        int times[N], tot = 0;
        bool occured[N];
        memset(occured, 0, sizeof(occured));
        memset(times, 0, sizeof(times));
        for (char ch : p) {
            int u = ch - 'a';
            times[u]++;
            occured[u] = true;
            tot++;
        }

        vector<int> res;
        // 用来抵消的拷贝值
        int tmptot = tot, tmptimes[N];
        memcpy(tmptimes, times, sizeof(times));
        for (int i = 0; i < (int) s.size(); i++) {
            // 清除头部的抵消记录,当然如果还没有多余的头则不用清除
            if (i - tot >= 0) {
                char ch = s[i - tot];
                int v = ch - 'a';
                // 只考察在 p 中出现过的字符
                if (occured[v]) {
                    // tmptimes[v] < 0 说明这个字符并未对 长度抵消记录 产生影响
                    if (tmptimes[v] >= 0)
                        tmptot++;
                    //
                    tmptimes[v]++;
                }
            }
            // 添加当前字符 ch 的抵消
            char ch = s[i];
            int u = ch - 'a';
            if (occured[u]) {
                // tmptimes[u] == 0 说明 p 中的对应字符都已经抵消了,
                // 这个字符也就不应该影响 长度抵消记录 了
                if (tmptimes[u] > 0)
                    tmptot--;
                tmptimes[u]--;
            }
            // 长度抵消记录 = 0,找到了一个 anagram
            if (tmptot == 0) {
                res.push_back(i - tot + 1);
            }
        }
        return res;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值