题干信息
Given two strings s and p, return an array of all the start indices of p’s anagrams in s. You may return the answer in any order.
Example 1:
Input: s = “cbaebabacd”, p = “abc”
Output: [0,6]
Explanation:
The substring with start index = 0 is “cba”, which is an anagram of “abc”.
The substring with start index = 6 is “bac”, which is an anagram of “abc”.
Example 2:
Input: s = “abab”, p = “ab”
Output: [0,1,2]
Explanation:
The substring with start index = 0 is “ab”, which is an anagram of “ab”.
The substring with start index = 1 is “ba”, which is an anagram of “ab”.
The substring with start index = 2 is “ab”, which is an anagram of “ab”.
Constraints:
1 <= s.length, p.length <= 3 * 104
s and p consist of lowercase English letters.
算法描述
这道题的算法本身很简单,但是我认为其正确性的证明是十分棘手的。首先先用自然语言描述其算法。
-
利用哈希表统计p串中每一个字符出现的次数记为哈希表A,并另设一个哈希表用于记录遍历过程中的信息,记为哈希表B。
-
初始时指针l和r都指向s串中第一个字符。
-
当r指针指向小于等于s串中的最后一个字符时,迭代执行如下步骤:
① 哈希表B中增加当前遍历到的r指针指向字符的次数。
②当该字符在哈希表B中出现的次数>该字符在哈希表A中出现的次数时,循环迭代执行如下步骤:
i)l指针向右移动
ii)删除哈希表B中对应字符的次数一次
③当l与r所对应子串长度与p一致时,将l加入最终结果中。增加r指针,继续循环
算法正确性证明
我们需要证明
- 命题1:加入的结果确实是异位子串
- 命题2:加入的结果包含了所有的异位子串
命题1的证明:假设加入的结果存在不是p的异位子串所对应的下标,那么在该子串中一定存在某个字符出现的次数大于或小于p中对应的次数。而大于是不可能的,如果存在大于的情况,那么在前面r到达这一字符时,由算法的描述l指针一定会向由移动从而使得相等而退出第二层循环,使得l不在当前所加入的子串对应的位置,这是矛盾的。于是每个字符出现的次数只可能小于或等于的关系。
另一方面,p串和当前子串的长度是相等的,当前子串中出现的所有字符一定在p串中都得以出现(由小于等于的性质保证的);如果p串中存在字符在当前串中没有出现,那么当前串的某一字符一定会比p存在更多次(注意当前串中存在的一定在p串中存在),又发生了矛盾。于是,p与当前串所含的字符集合是相等的。
最后,由于p和当前子串的长度相等,字符集相等,只可能小于等于的关系,每个字符的个数就必须相等了;
命题2的证明:在证明了命题1的基础上证明命题2。
- r指针逐步向右扫描,将所指示的字符次数增加到哈希表B中
- 使用类似数学归纳法的思路进行证明。考虑初始阶段,r指针逐步向右扫描,l不动,如果存在相等的情况则加入结果。显然不会遗漏l指在起始位置的所有结果。考虑如果需要进入第一次内层循环,表明r所字符出现的字符次数超出p中的规定了。此时可以将l指针向右移动而不会遗漏任何结果。这是因为循环只有在对应字符大于时才会执行,在循环判断成功时,对应的子串一定不会是想要的。直到循环退出时对应的子串才有可能是想要的。同时,对于每一个l值,向左边靠近一些的r值也是不存在的,否则这个内层循环就不是第一次了。
- 第一次内层循环退出后l指向了新的起始位置,l到最后可以看做是一个全新的串,外层循环继续以同样的方式进行作用在这个串上,问题的规模减小了,但算法的步骤是一致的。
- 综上所述,算法的正确性得到了证明