[leetcode] Valid Anagram 、 Find All Anagrams in a String

Valid Anagram

Given two strings s and , write a function to determine if t is an anagram of s.

Example 1:

Input: s = "anagram", t = "nagaram"
Output: true

Example 2:

Input: s = "rat", t = "car"
Output: false

Note:
You may assume the string contains only lowercase alphabets.

Follow up:
What if the inputs contain unicode characters? How would you adapt your solution to such case?


分析:题目要求判断两个字符串s和t是否是颠倒字母顺序构成的,比较简单,就是字符串t是否是s中的字符组成。这个题目看了一下有两个思路,一个直接对字符串排序,然后如果相等就是true,不等就是false;第二个思路就是用HashMap来做。因为比较简单,思路也比较清晰,就讨巧用python 一句话解决一下吧

1 class Solution(object):
2     def isAnagram(self, s, t):
3         """
4         :type s: str
5         :type t: str
6         :rtype: bool
7         """
8         return sorted(s) == sorted(t)

Find All Anagrams in a String

Given a string s and a non-empty string p, find all the start indices of p's anagrams in s.

Strings consists of lowercase English letters only and the length of both strings s and p will not be larger than 20,100.

The order of output does not matter.

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".

分析:这个题目要求再s中找t的字串位置(t可以是颠倒顺序的)。其实思路很简单,一个for循环过去进行对比就行了,关键就是在于如何确定两个字符串是否是颠倒的。刚开始我想的是用异或来做,可是代码写好之后发现有问题,比如s某个字串中有"aa",但是p中有"bb",那么用异或做的话就认为是相同的了,所以不能用异或做。白浪费了半个小时去找错,无语。。。


方法1:想到用一个list来保存p中的字符,然后去找s中的字串,如果s中某个字符再list中,就从list中删掉这个字符,最后如果list为空的话,就说明两个字符串是由相同的字符构成的了。代码如下:

 1 public List<Integer> findAnagrams(String s, String p) {
 2         List<Integer> res = new ArrayList<>();
 3         List<Character> list = new ArrayList<>();
 4         for ( char c : p.toCharArray() ) list.add(c);
 5         for ( int i = 0 ; i <= s.length() - p.length() ; i ++ ){
 6             List<Character> list_t = new ArrayList<>(list);
 7             boolean flag = true;
 8             for ( int j = i ; j < i + p.length() ; j ++ ){
 9                 if ( !list_t.contains(s.charAt(j)) ){
10                     flag = false;
11                     break;
12                 }
13                 list_t.remove((Object)s.charAt(j));
14             }
15             if ( flag && list_t.isEmpty() ) res.add(i);
16         }
17         return res;
18     }

结果跑到第34个用例就超时了,这说明设计题目的人可能就不想让我们借助一些简单的思路来做,还是得重新找最本质的解法。


方法2:窗口滑动法。做字符串匹配问题时,窗口滑动是非常适用的方法,因为它省去了很多重复的比较。窗口滑动的核心还是两个指针left、right。

      定义:结果:一个窗口内的字符恰好包含p中所有的字符,叫做一个结果。

      第一步,将map初始化,形成一个p中字符与出现次数对应的map。

      第二步,扩展窗口容量。然后初始化left和right为0,形成一个大小为0的窗口,然后右边界right不断向又探查,如果right位置的字符在map中,就令对应的value减1,该过程直到窗口中包含一个完整的结果为止,可以用一个counter变量来标识,counter初始值为map.size(),然后遇到一次p中的字符就减1,注意对于每个字符counter只减一次,然后当counter==0的时候,进入下一个步骤。

      第三步,我们要做的工作是对left进行动作,缩减窗口容量。当窗口包含一个结果以后,为了进一步遍历,我们需要缩小窗口使窗口不再包含全部的p,同样,如果map[char]>=0,表明一个在p中的字符就要移除窗口,那么count ++,以此类推。 然后通过窗口长度判断是否是正确答案。

    可以说滑动窗口这种思想,关键点在于: 

  • map中存储值的意义 
  • 窗口什么时候扩展和收缩,对应于left和right值什么时候发生变化。 

    在解题的时候,首先尝试扩展窗口right,看看什么时候包含了一个结果,记录结果。然后缩小左边界left,直到窗口不在包含一个可能解!接着就可以继续扩展窗口了,以此类推。

本题代码如下:

 1 public List<Integer> findAnagrams(String s, String p) {
 2         List<Integer> res = new ArrayList<>();
 3         Map<Character, Integer> map = new HashMap<>();
 4         for (char c : p.toCharArray()) map.put(c, map.getOrDefault(c, 0) + 1);
 5 
 6         int counter = map.size(); //代表窗口内是否包含p中全部的字符
 7         int left = 0;  //代表窗口的左边界
 8         int right = 0; //代表窗口的右边界
 9 
10         while (right < s.length()) {
11             char c = s.charAt(right);
12 
13             //下面这个代码段表示窗口右边界的扩展过程,扩展到left——right中包含p中全部的字符为止。
14             if (map.containsKey(c)) {
15                 map.put(c, map.get(c) - 1);
16                 if (map.get(c) == 0) counter--;  //表示p中的字符再窗口内出现一次
17             }
18             right++;
19 
20             //下面的代码查找窗口是否满足要求,缩小窗口直到窗口不包含一个可能的解
21             while (counter == 0) {   //counter==0的时候就是窗口内包含了p的全部字符
22                 char left_c = s.charAt(left);
23                 if (map.containsKey(left_c)) {
24                     map.put(left_c, map.get(left_c) + 1);
25                     if (map.get(left_c) > 0) counter++;  // 让右边界继续增大,寻找新的窗口
26                 }
27                 if (right - left == p.length()) res.add(left);
28                 left++;
29             }
30         }
31         return res;
32     }

在解决诸如substring这类问题时,不妨尝试想一想滑动窗口。

leetcode上还有一些题目都可以用类似的方法来求解。先mark一下,明天再处理这些问题。

https://leetcode.com/problems/minimum-window-substring/
https://leetcode.com/problems/longest-substring-without-repeating-characters/
https://leetcode.com/problems/substring-with-concatenation-of-all-words/
https://leetcode.com/problems/longest-substring-with-at-most-two-distinct-characters/
https://leetcode.com/problems/find-all-anagrams-in-a-string/

原文地址:https://www.cnblogs.com/boris1221/p/9265396.html